C#设计模式之适配器模式与装饰器模式的实现
作者:北冥虾 发布时间:2021-10-30 02:54:32
结构型设计模式
创建型设计模式主要是为了解决创建对象的问题,而结构型设计模式则是为了解决已有对象的使用问题。
适配器模式
适配器模式比较好理解,因为在我们的日常生活中就很常见,如耳机转换线、充电器适配器、插座等,举个最常见的例子:
插座就是个适配器,将一个接口扩展为多个接口,将墙上的双孔接口转换为三孔接口。而这也就是适配器的作用:将一个接口转换为用户期望的另一个接口。
适配器的使用场景:
需要使用第三方SDK的核心功能,但其接口或者功能不符合需求,这时可以使用适配器对其进行兼容和扩展
随着业务发展,旧接口已经不能满足需求,但重写代价又太大,这时可以使用适配器对接口功能进行扩展
注意:适配器是对已有资源进行兼容和扩展,属于一种折中的方式,如果可以的话,尽量重构系统而不是使用适配器
继承器的实现有两种方式:继承和组合,基于合成复用的原则,组合优于继承,所以应尽量使用组合的方式实现适配器。类图如下:
实现代码
//已有的旧接口,不兼容于现在的系统
public interface IAmericanElectrictService
{
int Get110VElectric();
}
//adaptee,需要适配的SDK
public class AmericanElectrictService : IAmericanElectrictService
{
public int Get110VElectric()
{
Console.WriteLine("美国的电压是110v,只能提供110V的电压");
return 110;
}
}
//已有接口,现在的系统需要使用这个接口
public interface IChineseElectricService
{
int Get220VElectric();
}
//适配器,采取组合的方式
//这里是为了适配已有接口,所以实现了这个接口
public class AdapterPattern : IChineseElectricService
{
private readonly IAmericanElectrictService _service;
public AdapterPattern(IAmericanElectrictService service)
{
this._service = service;
}
public int Get220VElectric()
{
var electric = this._service.Get110VElectric();
Console.WriteLine("劈里啪啦劈里啪啦,经过一番操作,现在电压转换为220V的了");
return electric + 110;
}
}
//使用适配器,将110V电压转换成220V
public class AdapterRunner : IRunner
{
public void Run()
{
//实际情况中,adaptee有可能是已有SDK,有可能是interface,通过IOC容器对应具体实现类
var americanElectric = new AmericanElectrictService();
var electric = americanElectric.Get110VElectric();
Console.WriteLine($"获得了{electric}V电压");
Console.WriteLine("使用适配器");
var adapter = new AdapterPattern(americanElectric);
electric = adapter.Get220VElectric();
Console.WriteLine($"使用适配器后获得了{electric}V电压");
}
}
//输出
//------------------------------------
//美国的电压是110v,只能提供110V的电压
//获得了110V电压
//使用适配器
//美国的电压是110v,只能提供110V的电压
//劈里啪啦劈里啪啦,经过一番操作,现在电压转换为220V的了
//使用适配器后获得了220V电压
总结
优点:
可以扩展和兼容现有类,灵活性高
提高了类的复用,原本不能使用的类适配后能使用
缺点:
适配器本质是套一层,如果使用过多,可能导致系统混乱,甚至出现套中套的复杂情况
装饰器模式
利用继承和组合,在不改变现有结构的情况下对功能进行扩展的模式称为装饰器模式
装饰器模式和适配器模式很像,但侧重点不一样。适配器的重心在于兼容已有系统,而装饰器的重心在于功能扩展。装饰器的类图如下:
上图中,基础装饰器继承抽象类,每个装饰器继承前一个装饰器,一步一步添加功能,并且所有装饰器都用到具体实现类,因为需要扩展具体功能。
这里其实就能看出一些装饰器和适配器的区别,适配器和装饰器都使用组合来包装已有类,不同的是装饰器用到了继承。装饰器的核心原则是里氏替换原则,即父类一定能被子类替换而不影响现有代码。
实现代码
//抽象基础类
public abstract class AbstractStudent
{
public abstract void Study();
}
//具体实现类
public class Student : AbstractStudent
{
public override void Study()
{
Console.WriteLine("我正在学习!!!");
}
}
//基础装饰器,什么也不做
//注意,这里标记为抽象类,此后的装饰器以此为基础
public abstract class BaseDecorator : AbstractStudent
{
private readonly AbstractStudent _student;
public BaseDecorator(AbstractStudent student)
{
this._student = student;
}
//这里使用override还是Virtual取决于AbstractStudent基础类是抽象类还是接口
public override void Study()
{
this._student.Study();
}
}
//前缀装饰器,在调用具体功能前做点什么
public class PreDecorator : BaseDecorator
{
public PreDecorator(AbstractStudent student) : base(student)
{
}
public override void Study()
{
Console.WriteLine("学习前看会儿小说");
base.Study();
}
}
//后缀装饰器,在调用具体功能后做点什么
public class NextDecorator : PreDecorator
{
public NextDecorator(AbstractStudent student) : base(student)
{
}
public override void Study()
{
base.Study();
Console.WriteLine("学习辛苦啦,奖励自己一包辣条");
}
}
//测试代码
public class DecoratorRunner : IRunner
{
public void Run()
{
Console.WriteLine("没有用装饰器的基本功能:");
var student = new Student();
student.Study();
Console.WriteLine();
Console.WriteLine("使用前缀装饰器在基础功能之前做点什么");
var preDecorator = new PreDecorator(student);
preDecorator.Study();
Console.WriteLine();
Console.WriteLine("使用后缀装饰器在前缀装饰器功能之后做点什么");
//注意:这里传入的前缀装饰器,在前缀装饰器的基础之上做扩展
var nextDecorator = new NextDecorator(student);
nextDecorator.Study();
}
}
//输出:
//没有用装饰器的基本功能:
//我正在学习!!!
//
//使用前缀装饰器在基础功能之前做点什么
//学习前看会儿小说
//我正在学习!!!
//
//使用后缀装饰器在前缀装饰器功能之后做点什么
//学习前看会儿小说
//我正在学习!!!
//学习辛苦啦,奖励自己一包辣条
可以看出,装饰器其实就是利用组合+继承(实现)+override不断包装和更新对象,使其功能得到扩展。装饰器是用于替换继承的设计模式,主要使用场景如下:
想扩展实现类的功能,又不想添加太多子类
需要动态增加和撤销功能(例如游戏技能)
装饰器的优点在于灵活,耦合性低,且不会改变现有结构。缺点则是嵌套过多会增加系统复杂度。
来源:https://www.cnblogs.com/damocleses/p/16194597.html


猜你喜欢
- 一、POI的定义JAVA中操作Excel的有两种比较主流的工具包: JXL 和 POI 。jxl 只能操作Excel 95, 97, 200
- Java中有两种处理异常的方式,分别是用throws抛出异常、用try、catch捕获异常。try-catch在Javatry-catch语
- 我们写的主类中的main()方法是如何被Java虚拟机调用到的?在Java类中的一些方法会被由C/C++编写的HotSpot虚拟机的C/C+
- 哈夫曼(Huffman)编码是一种常用的压缩编码方法,是 Huffman 于 1952 年为压缩文本文件建立的。它的基本原理是频繁使用的数据
- 本文以一个实例简单实现了类的创建与初始化,实现代码如下所示:using System;using System.Collections.Ge
- 目录1、Java Application 源程序的主类是指包含有( )方法的类。2、如果定义一种表达式结构:(+ 6 3)的值为9,(- 6
- 本文实例讲述了.NET WinForm实现在listview中添加progressbar的方法。分享给大家供大家参考,具体如下:找了好长时间
- 异常方法//返回此可抛出对象的详细信息消息字符串public String getMessage() //将此可抛发对象及其回溯到标准错误流
- 要将一个对象序列化,可是如果对象的属性为null的时候,我们想将属性为null的都去掉。在这里我使用Newtonsoft.Json.dll记
- 最近做项目,碰到如下的需求:ViewPager分页,如果是6页(包括6页)就用圆点,如果是6页以上就用进度条来切换。前面一种交互方法最常见,
- 在观察者模式中有2个要素:一个是被观察对象,另一个是观察者。但被观察对象的状态发生改变会通知观察者。举例:把订阅报纸的人看作是观察者,把报纸
- 使用@Async异步调用方法Async简介异步方法调用使用场景:处理日志、发送邮件、短信......spring中提供了@Async来实现异
- springboot 配置服务代理有时候,我们可能有下边这样的需求:即,针对于分布式服务,我们会有多种业务接口服务,但是服务器上可能只要求开
- 我们还是用一个小例子来看看自定义View和自定义属性的使用,带大家来自己定义一个带进度的圆形进度条,我们还是先看一下效果吧从上面可以看出,我
- 引入假定我们有两个项目:Cup 和 Water,其中 Cup 表示主项目,而 Water 表示子模块项目。其中 Cup 的远程仓库地址为 g
- Map是键值对的集合,又叫作字典或关联数组等,是最常见的数据结构之一。在java如何让一个map按value排序呢? 看似简单,但却不容易!
- 本文实例讲述了Android开发之菜单(menu)用法。分享给大家供大家参考,具体如下:Android手机专门用一个按键“menu“来显示菜
- 一、在drawable下面添加xml文件rounded_editview.xml<?xml version="1.0&quo
- 一、网络爬虫的基本知识网络爬虫通过遍历互联网络,把网络中的相关网页全部抓取过来,这体现了爬的概念。爬虫如何遍历网络呢,互联网可以看做是一张大
- 改了个bug,发现这个东西以前不知道,搜索了一下,看到的都是长篇大论,还谈js的源码,也是醉了。我就简单的说说这个是干啥的。简单说:就是触发