结构型模式(一) 适配器模式(Adapter)
一、动机(Motivation)
在软件系统中,由于应用环境的变化,常常需要将“一些现存的对象”放在新的环境中应用,但是新环境要求的接口是这些现存对象所不满足的。
如何应对这种“迁移的变化”?如何既能利用现有对象的良好实现,同时又能满足新的应用环境所要求的接口?
二、意图(Intent)
将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
例说Adapter应用
这种实际上是一种委派的调用,本来是发送请求给MyStack,但是MyStack实际上是委派给list去处理。MyStack在这里其实就是Adapter(适配对象),list即是Adaptee(被适配的对象),而IStack就是客户期望的接口。太直接了,没什么可说的。
三、结构(Structure)
适配器有两种结构
1、对象适配器(更常用)
对象适配器使用的是对象组合的方案,它的Adapter和Adaptee的关系是组合关系,即上面例子中MyStack和list是组合关系。
OO中优先使用组合模式,组合模式不适用再考虑继承。因为组合模式更加松耦合,而继承是紧耦合的,父类的任何改动都要导致子类的改动。
上面的例子就是对象适配器。
2、类适配器(不推荐使用)
下面的例子是类适配器。
Adapter继承了ArrayList,也继承了IStack接口,它既可以使用ArrayList里的方法,也可以使用IStack接口里的方法,这样就感觉有点不伦不类。这个类违反了类应该具有单一职责的原则,它既有ArrayList的职责,也有IStack的职责,因此这种类适配不是很常用,也不推荐使用。
注意:如果一个方法有可能要委托到2个或2个以上的对象,或者2个或2个以上的类需要委托,对于对象适配器,只需要增加几个内部的属性就可以实现适配。
而对于类适配器,因为C#中类只能是单一继承,它不能继承自2个或2个以上的类,所以类适配器这里便无法使用。
四、模式的组成
可以看出,在适配器模式的结构图有以下角色:
(1)、目标角色(Target):定义Client使用的与特定领域相关的接口。
(2)、客户角色(Client):与符合Target接口的对象协同。
(3)、被适配角色(Adaptee):定义一个已经存在并已经使用的接口,这个接口需要适配。
(4)、适配器角色(Adapte) :适配器模式的核心。它将对被适配Adaptee角色已有的接口转换为目标角色Target匹配的接口。对Adaptee的接口与Target接口进行适配.
五、 适配器模式的具体实现
实现一个对栈的操作,有一个IStact接口,里面有三个方法Push(进栈)、Pop(出栈)和GetTopItem(取最顶层元素),这个IStact接口将相当于上面的Target,想要实现进栈出栈的操作,如果自己去实现数据结构显得比较麻烦,在此可以将net提供的ArrayList类拿来一用,ArrayList类就是被适配的对象,相当于上面的Adaptee。在写一个适配类StactAdapter类完成功能就可以了。
/// <summary>
/// 栈的接口
/// </summary>
public interface IStack
{
void Push(object item);
void Pop();
Object GetTopItem();
}
/// <summary>
/// 对象适配器
/// </summary>
public class StactAdapter : IStack
{
ArrayList list;
/// <summary>
/// 构造函数中实例化ArrayList
/// </summary>
public StactAdapter()
{
list = new ArrayList();
}
/// <summary>
/// 进栈
/// </summary>
/// <param name="item">压入栈的元素</param>
public void Push(object item)
{
list.Add(item);
}
/// <summary>
/// 出栈
/// </summary>
public void Pop()
{
list.RemoveAt(list.Count - 1);
}
/// <summary>
/// 取最顶层的元素
/// </summary>
/// <returns></returns>
public Object GetTopItem()
{
return list[list.Count - 1];
}
}
/// <summary>
/// 客户调用
/// </summary>
public class App
{
static void Main(string[] args)
{
IStack myStack = new StactAdapter();
myStack.Push("oec2003");
myStack.Push("oec2004");
myStack.Push("oec2005");
myStack.Pop();
Console.WriteLine(myStack.GetTopItem());
}
}
六、适配器模式的实现要点:
1、Adapter模式主要应用于“希望复用一些现存的类,但是接口又与复用环境要求不一致的情况”,在遗留代码复用、类库迁移等方面非常有用。
2、GoF23定义了两种Adapter模式的实现结构:对象适配器和类适配器。类适配器采用“多继承”的实现方式,在C#语言中,如果被适配角色是类,Target的实现只能是接口,因为C#语言只支持接口的多继承的特性。在C#语言中类适配器也很难支持适配多个对象的情况,同时也会带来了不良的高耦合和违反类的职责单一的原则,所以一般不推荐使用。对象适配器采用“对象组合”的方式,更符合松耦合精神,对适配的对象也没限制,可以一个,也可以多个,但是,使得重定义Adaptee的行为较困难,这就需要生成Adaptee的子类并且使得Adapter引用这个子类而不是引用Adaptee本身。Adapter模式可以实现的非常灵活,不必拘泥于GoF23中定义的两种结构。例如,完全可以将Adapter模式中的“现存对象”作为新的接口方法参数,来达到适配的目的。
3、Adapter模式本身要求我们尽可能地使用“面向接口的编程”风格,这样才能在后期很方便地适配。
适配器模式用来解决现有对象与客户端期待接口不一致的问题,下面详细总结下适配器两种形式的优缺点。
1、类的适配器模式:
优点:
(1)、可以在不修改原有代码的基础上来复用现有类,很好地符合 “开闭原则”
(2)、可以重新定义Adaptee(被适配的类)的部分行为,因为在类适配器模式中,Adapter是Adaptee的子类
(3)、仅仅引入一个对象,并不需要额外的字段来引用Adaptee实例(这个即是优点也是缺点)。
缺点:
(1)、用一个具体的Adapter类对Adaptee和Target进行匹配,当如果想要匹配一个类以及所有它的子类时,类的适配器模式就不能胜任了。因为类的适配器模式中没有引入Adaptee的实例,光调用this.SpecificRequest方法并不能去调用它对应子类的SpecificRequest方法。
(2)、采用了 “多继承”的实现方式,带来了不良的高耦合。
2、对象的适配器模式
优点:
(1)、可以在不修改原有代码的基础上来复用现有类,很好地符合 “开闭原则”(这点是两种实现方式都具有的)
(2)、采用 “对象组合”的方式,更符合松耦合。
缺点:
(1)、使得重定义Adaptee的行为较困难,这就需要生成Adaptee的子类并且使得Adapter引用这个子类而不是引用Adaptee本身。
3、适配器模式使用的场景:
(1)、系统需要复用现有类,而该类的接口不符合系统的需求
(2)、想要建立一个可重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
(3)、对于对象适配器模式,在设计里需要改变多个已有子类的接口,如果使用类的适配器模式,就要针对每一个子类做一个适配器,而这不太实际。
七、.NET 中适配器模式的实现
说道适配器模式在Net中的实现就很多了,比如:System.IO里面的很多类都有适配器的影子,当我们操作文件的时候,其实里面调用了COM的接口实现。以下两点也是适配器使用的案例:
1.在.NET中复用COM对象:
COM对象不符合.NET对象的接口,使用tlbimp.exe来创建一个Runtime Callable Wrapper(RCW)以使其符合.NET对象的接口,COM Interop就好像是COM和.NET之间的一座桥梁。
2..NET数据访问类(Adapter变体):
各种数据库并没有提供DataSet接口,使用DbDataAdapter可以将任何个数据库访问/存取适配到一个DataSet对象上,DbDataAdapter在数据库和DataSet之间做了很好的适配。当然还有SqlDataAdapter类型了,针对微软SqlServer类型的数据库在和DataSet之间进行适配。
结构型模式(一) 适配器模式(Adapter)的更多相关文章
- 设计模式---结构型模式之适配器模式(Adapter Pattern)
适配器模式定义 将一个类的接口,转换成客户期望的另外一个接口.适配器让原本接口不兼容的类可以合作无间. 适配器模式主要有两种类型:对象适配器和类适配器. 在详细解释这两种类型时,解释部分重要角色.生活 ...
- Java设计模式(7)——结构型模式之适配器模式(Adapter)
一.概述 概念 其实,举个生活中的例子的话,适配器模式可以类比转接头,比如typeC和USB的转接头,把原本只能接typeC的接口,拓展为可以接普通USB:这里的转接头一方面需要查在typeC上,一方 ...
- 结构型设计模式之适配器模式(Adapter)
结构 意图 将一个类的接口转换成客户希望的另外一个接口.A d a p t e r 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作. 适用性 你想使用一个已经存在的类,而它的接口不符合你 ...
- GoF23种设计模式之结构型模式之适配器模式
一.概述 将一个类的接口转换成客户希望的另外一个接口.适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作. 二.适用性 1.你想使用一个已经存在的类,但是它的接口不符合 ...
- Java设计模式——结构型模式
Java设计模式中共有7种结构型模式:适配器模式.装饰模式.代理模式.外观模式.桥接模式.组合模式.享元模式.其中对象的适配器模式是各种模式的起源,其关系如下面的图:1.适配器模式 适配器模式将某个类 ...
- NET设计模式 第三部分 结构型模式(7):适配器模式(Adapter Pattern)
适配器模式(Adapter Pattern) ——.NET设计模式系列之八 Terrylee,2006年2月 概述 在软件系统中,由于应用环境的变化,常常需要将“一些现存的对象”放在新的环境中应用,但 ...
- 设计模式06: Adapter 适配器模式(结构型模式)
Adapter 适配器模式(结构型模式) 适配(转换)的概念无处不在:电源转接头.电源适配器.水管转接头... 动机(Motivation)在软件系统中,由于应用环境的变化,常常需要将“一些现存的对象 ...
- 初探Java设计模式2:结构型模式(代理模式,适配器模式等)
行为型模式 行为型模式关注的是各个类之间的相互作用,将职责划分清楚,使得我们的代码更加地清晰. 策略模式 策略模式太常用了,所以把它放到最前面进行介绍.它比较简单,我就不废话,直接用代码说事吧. 下面 ...
- Java设计模式之结构型模式
结构型模式,共七种:适配器模式.装饰器模式.代理模式.外观模式.桥接模式.组合模式.享元模式. 一.适配器模式: 意图: 将一个类的接口转换成客户希望的另外一个接口.Adapter 模式使得原本由于接 ...
随机推荐
- Mac OS备份迁移iBooks图书操作方法
前段时间换电脑,需要把原本电脑上的一些文件备份.迁移出来,包括iBooks中的电子书. 理论上,苹果体系中通过icloud账号可以把通讯录.备忘录等东西同步过去,但查了一下发现图书支持有限,而且我的e ...
- Mysql中的读锁,写锁,乐观锁及事务隔离级别和并发问题
mysql读锁,写锁,乐观锁 读锁,也叫共享锁(shared lock) SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE 写锁,也叫排他 ...
- [转帖]GNU, Free Software and Open Source 自由软件与开源软件
GNU, Free Software and Open Source 自由软件与开源软件 https://blog.csdn.net/icycolawater/article/details/7792 ...
- Kafka Broker | 命令行选项和过程
1.目标 在这个Apache Kafka教程中,我们将学习Kafka Broker.Kafka Broker管理主题中的消息存储.如果Apache Kafka有多个代理,那就是我们所说的Kafka集群 ...
- 向php数组函数array_colum()传入奇怪的数组
<?php // 向php数组函数array_colum()传入奇怪的数组 // array_colum()函数 返回行列数组的其中一列,可以用其他列的键充当键 $arr = [ [ 1, 2, ...
- Django学习之django自带的contentType表
Django学习之django自带的contentType表 通过django的contentType表来搞定一个表里面有多个外键的简单处理: 摘自:https://blog.csdn.net/aar ...
- 【scratch3.0教程】2.2 魔力手环
编程前的准备 在设计一个作品之前,必须先策划一个脚本,然后再根据脚本,收集或制作素材(图案,声音等)接着就可以启动Scratch,汇入角色,舞台,利用搭程序积木的方式编辑程序,制作出符合脚本的动画或游 ...
- 使用Jenkins自带功能(不用shell)构建Docker镜像并推送到远程仓库
意义: 一开始实现这个目的是在Jenkins中使用的shell脚本,也就是如下的这个: bash # 进入到生成jar包的根目录 cd ${WORKSPACE}/${module_filename} ...
- JavaScript中数组的key-value在对象中倒装的妙用
对于数组的去重.寻找指定元素的索引,通常我们都是通过遍历来解决,但是在某些应用场景下,将数组的value-key进行倒装,也即将value当做对象的key,key当做对象value,可以极大降低算法的 ...
- iTextSharp 不适用模板 代码拼接PDF
/// <summary> /// 打印移库单 /// </summary> /// <param name="guid"></param ...