设计模式(五)适配器模式Adapter(结构型)
1. 概述:
接口的改变,是一个需要程序员们必须(虽然很不情愿)接受和处理的普遍问题。程序提供者们修改他们的代码;系统库被修正;各种程序语言以及相关库的发展和进化。
例子1:iphone4,你即可以使用UBS接口连接电脑来充电,假如只有iphone没有电脑,怎么办呢?苹果提供了iphone电源适配器。可以使用这个电源适配器充电。这个iphone的电源适配器就是类似我们说的适配器模式。(电源适配器就是把电源变成需要的电压,也就是适配器的作用是使得一个东西适合另外一个东西。)
例子2:最典型的例子就是很多功能手机,每一种机型都自带有从电器,有一天自带充电器坏了,而且市场没有这类型充电器可买了。怎么办?万能充电器就可以解决。这个万能充电器就是适配器。
2. 问题
你如何避免因外部库的API改变而带来的不便?假如你写了一个库,你能否提供一种方法允许你软件的现有用户进行完美地升级,即使你已经改变了你的API?为了更好地适宜于你的需要,你应该如何改变一个对象的接口?
3. 解决方案
适配器(Adapter)模式为对象提供了一种完全不同的接口。你可以运用适配器(Adapter)来实现一个不同的类的常见接口,同时避免了因升级和拆解客户代码所引起的纠纷。
适配器模式(Adapter Pattern),把一个类的接口变换成客户端所期待的另一种接口, Adapter模式使原本因接口不匹配(或者不兼容)而无法在一起工作的两个类能够在一起工作。又称为转换器模式、变压器模式、包装(Wrapper)器模式(把已有的一些类包装起来,使之能有满足需要的接口)。考虑一下当(不是假设!)一个第三方库的API改变将会发生什么。过去你只能是咬紧牙关修改所有的客户代码,而情况往往还不那么简单。你可能正从事一项新的项目,它要用到新版本的库所带来的特性,但你已经拥有许多旧的应用程序,并且它们与以前旧版本的库交互运行地很好。你将无法证明这些新特性的利用价值,如果这次升级意味着将要涉及到其它应用程序的客户代码。
4. 分类
共有两类适配器模式:1.类的适配器模式(采用继承实现)2.对象适配器(采用对象组合方式实现)
1)类适配器模式 ——适配器继承自已实现的类(一般多重继承)。
Adapter与Adaptee是继承关系
1、用一个具体的Adapter类和Target进行匹配。结果是当我们想要一个匹配一个类以及所有它的子类时,类Adapter将不能胜任工作
2、使得Adapter可以重定义Adaptee的部分行为,因为Adapter是Adaptee的一个子集
3、仅仅引入一个对象,并不需要额外的指针以间接取得adaptee2)对象适配器模式—— 适配器容纳一个它包裹的类的实例。在这种情况下,适配器调用被包裹对象的物理实体。
Adapter与Adaptee是委托关系
1、允许一个Adapter与多个Adaptee同时工作。Adapter也可以一次给所有的Adaptee添加功能
2、使用重定义Adaptee的行为比较困难
无论哪种适配器,它的宗旨都是:保留现有类所提供的服务,向客户提供接口,以满足客户的期望。
即在不改变原有系统的基础上,提供新的接口服务。
5. 适用性
以下情况使用Adapter模式:
1 • 你想使用一个已经存在的类,而它的接口不符合你的需求。
2 • 你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。
3 •(仅适用于对象Adapter)你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。即仅仅引入一个对象,并不需要额外的指针以间接取得adaptee。
6. 结构
类适配器使用多重继承对一个接口与另一个接口进行匹配,如下图所示:
对象匹配器依赖于对象组合,如下图所示:
7. 构建模式的组成
•目标角色(Target):— 定义Client使用的与特定领域相关的接口。
•客户角色(Client):与符合Target接口的对象协同。
• 被适配橘色(Adaptee):定义一个已经存在并已经使用的接口,这个接口需要适配。
•适配器角色(Adapte):适配器模式的核心。它将对被适配Adaptee角色已有的接口转换为目标角色Target匹配的接口。对Adaptee的接口与Target接口进行适配.
8. 效果
类适配器和对象适配器有不同的权衡。
类适配器
• 用一个具体的Adapter类对Adaptee和Target进行匹配。结果是当我们想要匹配一个类以及所有它的子类时,类Adapter将不能胜任工作。
• 使得Adapter可以重定义Adaptee的部分行为,因为Adapter是Adaptee的一个子类。
• 仅仅引入了一个对象,并不需要额外的指针以间接得到 Adaptee。
对象适配器则
• 允许一个Adapter与多个Adaptee—即Adaptee本身以及它的所有子类(如果有子类的话)—同时工作。Adapter也可以一次给所有的Adaptee添加功能。
• 使得重定义Adaptee的行为比较困难。这就需要生成Adaptee的子类并且使得Adapter引用这个子类而不是引用Adaptee本身。使用Adapter模式时需要考虑的其他一些因素有:
1) Adapter的匹配程度 对Adaptee的接口与Target的接口进行匹配的工作量各个Adapter可能不一样。工作范围可能是,从简单的接口转换(例如改变操作名 )到支持完全不同的操作集合。Adapter的工作量取决于Target接口与Adaptee接口的相似程度
2) 可插入的Adapter 当其他的类使用一个类时,如果所需的假定条件越少,这个类就更具可复用性。如果将接口匹配构建为一个类,
就不需要假定对其他的类可见的是一个相同的接口。也就是说,接口匹配使得我们可以将自己的类加入到一些现有的系统中去,
而这些系统对这个类的接口可能会有所不同。
3) 使用双向适配器提供透明操作 使用适配器的一个潜在问题是,它们不对所有的客户都透明。被适配的对象不再兼容 Adaptee的接口,
因此并不是所有 Adaptee对象可以被使用的地方它都可以被使用。双向适配器提供了这样的透明性。
在两个不同的客户需要用不同的方式查看同一个对象时,双向适配器尤其有用。
9. 实现
类适配器使用的是继承
让我们看看当API改变时,如何保护应用程序不受影响。
- <?php
- /**
- * 类适配器模式
- * @author guisu
- *
- */
- /**
- * 目标角色
- * @version 1.0
- */
- class Target {
- /**
- * 这个方法将来有可能改进
- */
- public function hello(){
- echo 'Hello ';
- }
- /**
- * 目标点
- */
- public function world(){
- echo 'world';
- }
- }
- /**
- * Client 程序
- *
- */
- class Client {
- /**
- * Main program.
- */
- public static function main() {
- $Target = new Target();
- $Target->hello();
- $Target->world();
- }
- }
- Client::main();
- ?>
我们Target已经明确指出hello()方法会在未来的版本中改进,甚至不被支持或者淘汰。接下来,现在假设第二版的Target已经发布。一个全新的greet()方法代替了hello()。
- <?php
- /**
- * 类适配器模式
- * @author guisu
- *
- */
- /**
- * 目标角色
- * @version 2.0
- */
- class Target {
- /**
- * 这个方法将来有可能继续改进
- */
- public function greet(){
- echo 'Greet ';
- }
- /**
- * 目标点
- */
- public function world(){
- echo 'world';
- }
- }
如果我们继续使用原来的client代码,肯定会报错,找不到hello方法。
针对API“升级”的解决办法就是创建一个适配器(Adapter)。
类适配器使用的是继承:
- <?php
- /**
- * 类适配器模式
- * @author guisu
- *
- */
- /**
- * 目标角色
- * @version 2.0
- */
- interface Target {
- /**
- * 源类的方法:这个方法将来有可能继续改进
- */
- public function hello();
- /**
- * 目标点
- */
- public function world();
- }
- /**
- * 源角色:被适配的角色
- */
- class Adaptee {
- /**
- * 源类含有的方法
- */
- public function world() {
- echo ' world <br />';
- }
- /**
- * 加入新的方法
- */
- public function greet() {
- echo ' Greet ';
- }
- }
- /**
- * 类适配器角色
- */
- class Adapter extends Adaptee implements Target {
- /**
- * 源类中没有world方法,在此补充
- */
- public function hello() {
- parent::greet();
- }
- }
- /**
- * 客户端程序
- *
- */
- class Client {
- /**
- * Main program.
- */
- public static function main() {
- $adapter = new Adapter();
- $adapter->hello();
- $adapter->world();
- }
- }
- Client::main();
- ?>
对象适配器使用的是委派
- <?php
- /**
- * 类适配器模式
- * @author guisu
- *
- */
- /**
- * 目标角色
- * @version 2.0
- */
- interface Target {
- /**
- * 源类的方法:这个方法将来有可能继续改进
- */
- public function hello();
- /**
- * 目标点
- */
- public function world();
- }
- /**
- * 源角色:被适配的角色
- */
- class Adaptee {
- /**
- * 源类含有的方法
- */
- public function world() {
- echo ' world <br />';
- }
- /**
- * 加入新的方法
- */
- public function greet() {
- echo ' Greet ';
- }
- }
- /**
- * 类适配器角色
- */
- class Adapter implements Target {
- private $_adaptee;
- /**
- * construct
- *
- * @param Adaptee $adaptee
- */
- public function __construct(Adaptee $adaptee) {
- $this->_adaptee = $adaptee;
- }
- /**
- * 源类中没有world方法,在此补充
- */
- public function hello() {
- $this->_adaptee->greet();
- }
- /**
- * 源类中没有world方法,在此补充
- */
- public function world() {
- $this->_adaptee->world();
- }
- }
- /**
- * 客户端程序
- *
- */
- class Client {
- /**
- * Main program.
- */
- public static function main() {
- $adaptee = new Adaptee();
- $adapter = new Adapter($adaptee);
- $adapter->hello();
- $adapter->world();
- }
- }
- Client::main();
- ?>
如例中代码所示,你可以运用适配器(Adapter)模式来避免因外部库改变所带来的不便——倘若向上兼容。作为某个库的开发者,你应该独立编写适配器,使你的用户更简便地使用新版本的库,而不用去修改他们现有的全部代码。
GoF书中提出的适配器(Adapter)模式更倾向于运用继承而不是组成。这在强类型语言中是有利的,因为适配器(Adapter)事实上是一个目标类的子类,因而能更好地与类中方法相结合。
了更好的灵活性,我个人比较倾向于组成的方法(特别是在结合了依赖性倒置的情况下);尽管如此,继承的方法提供两种版本的接口,或许在你的实际运用中反而是一个提高灵活性的关键。
10.适配器模式与其它相关模式
桥梁模式(bridge模式):桥梁模式与对象适配器类似,但是桥梁模式的出发点不同:桥梁模式目的是将接口部分和实现部分分离,从而对它们可以较为容易也相对独立的加以改变。而对象适配器模式则意味着改变一个已有对象的接口
装饰器模式(decorator模式):装饰模式增强了其他对象的功能而同时又不改变它的接口。因此装饰模式对应用的透明性比适配器更好。结果是decorator模式支持递归组合,而纯粹使用适配器是不可能实现这一点的。
Facade(外观模式):适配器模式的重点是改变一个单独类的API。Facade的目的是给由许多对象构成的整个子系统,提供更为简洁的接口。而适配器模式就是封装一个单独类,适配器模式经常用在需要第三方API协同工作的场合,设法把你的代码与第三方库隔离开来。
适配器模式与外观模式都是对现相存系统的封装。但这两种模式的意图完全不同,前者使现存系统与正在设计的系统协同工作而后者则为现存系统提供一个更为方便的访问接口。简单地说,适配器模式为事后设计,而外观模式则必须事前设计,因为系统依靠于外观。总之,适配器模式没有引入新的接口,而外观模式则定义了一个全新的接口。
代理模式(Proxy )在不改变它的接口的条件下,为另一个对象定义了一个代理。
装饰者模式,适配器模式,外观模式三者之间的区别:
装饰者模式的话,它并不会改变接口,而是将一个一个的接口进行装饰,也就是添加新的功能。
适配器模式是将一个接口通过适配来间接转换为另一个接口。
外观模式的话,其主要是提供一个整洁的一致的接口给客户端。
设计模式(五)适配器模式Adapter(结构型)的更多相关文章
- 适配器模式 adapter 结构型 设计模式(九)
现实世界中的适配器模型 先来看下来几个图片,截图自淘宝 上图为港版的插头与港版的插座 上图为插座适配器卖家的描述图 上图为适配后的结果 现实世界中适配器模式 角色分类 这就是适配器模式在电 ...
- Java设计模式06:常用设计模式之适配器模式(结构型模式)
1. Java之适配器模式(Adapter Pattern) (1)概述: 将一个类的接口转换成客户希望的另外一个接口.Adapter模式使得原本由于接口不兼容而不能一起工作的那些类,可以在一起 ...
- 设计模式06: Adapter 适配器模式(结构型模式)
Adapter 适配器模式(结构型模式) 适配(转换)的概念无处不在:电源转接头.电源适配器.水管转接头... 动机(Motivation)在软件系统中,由于应用环境的变化,常常需要将“一些现存的对象 ...
- 怎样让孩子爱上设计模式 —— 7.适配器模式(Adapter Pattern)
怎样让孩子爱上设计模式 -- 7.适配器模式(Adapter Pattern) 标签: 设计模式初涉 概念相关 定义: 适配器模式把一个类的接口变换成client所期待的还有一种接口,从而 使原本因接 ...
- 设计模式之适配器模式(Adapter)
适配器模式原理:适配器模式属于结构型模式,主要作用是完成功能的转换, 1.通过一个类继承目标类. 2.需要适配的类 3.适配器 代码如下: #include <iostream> usin ...
- 乐在其中设计模式(C#) - 适配器模式(Adapter Pattern)
原文:乐在其中设计模式(C#) - 适配器模式(Adapter Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 适配器模式(Adapter Pattern) 作者:webabc ...
- 8.3 GOF设计模式二: 适配器模式 Adapter
GOF设计模式二: 适配器模式 Adapter 为中国市场生产的电器,到了美国,需要有一个转接器才能使用墙上的插座,这个转接 器的功能.原理?复习单实例模式 SingleTon的三个关键点 ...
- NET设计模式 第三部分 结构型模式(7):适配器模式(Adapter Pattern)
适配器模式(Adapter Pattern) ——.NET设计模式系列之八 Terrylee,2006年2月 概述 在软件系统中,由于应用环境的变化,常常需要将“一些现存的对象”放在新的环境中应用,但 ...
- 【设计模式】适配器模式 Adapter Pattern
适配器模式在软件开发界使用及其广泛,在工业界,现实中也是屡见不鲜.比如手机充电器,笔记本充电器,广播接收器,电视接收器等等.都是适配器. 适配器主要作用是让本来不兼容的两个事物兼容和谐的一起工作.比如 ...
- php设计模式五----适配器模式
1.简介 适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁.这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能. 意图:将一个类的接口转换成客户希望的另外一个接口 ...
随机推荐
- iOS开发针对SQL语句的封装
1.使用依赖关系 a.需要添加libsqlite3.tbd 静态库. b.需要添加第三方框架 FMBD.MJExtension. 2. SQL语句类封装名DataBaseSqlTool 类方法介绍 ...
- 【随手记-有空整理】使用PHPMailer和QQ邮箱实现邮件的自动发送
使用PHPMailer和QQ邮箱发送邮件时,在密码一栏里要填写QQ邮箱提供的授权码,而不是邮箱的登录密码 授权码获取可参考:http://service.mail.qq.com/cgi-bin/hel ...
- U-Boot在FL2440上移植(一)----修改系统时钟
<一>U-boot源码结构 图片来自韦东山的<嵌入式linux应用开发完全手册> U-boot下载地址:ftp://ftp.denx.de/pub/u-boot/ 交叉编译工具 ...
- 啊上班的二号i将诶
http://www.huihui.cn/share/8112372 http://www.huihui.cn/share/8112363 http://www.huihui.cn/share/811 ...
- 【jQuery】使用JQ来编写面板的淡入淡出效果
本文与上一篇的<[jQuery]使用JQ来编写最主要的淡入淡出效果>(点击打开链接)为姊妹篇. 但上一篇仅仅是对文本的基本控制,本篇则是对面板元素进行控制. 尽管功能上很类似,可是所用到的 ...
- JS判断只能是数字和小数点
JS判断只能是数字和小数点 1.文本框只能输入数字代码(小数点也不能输入) <input onkeyup="this.value=this.value.replace(/\D/g,'' ...
- android面试题之一
在接下来的一段时间,我将收集一些常见面试题,综合网上资料加自己测试与理解,将其总结出来和大家分享,里面难免有一些问题,希望大家提出宝贵意见以便及时更正. 一.Activity.Service.Broa ...
- Android图像篇
Android的渲染分为2D渲染和3D渲染两种,当中2D渲染的引擎为Skia.3D渲染的引擎是OpenGL ES.眼下.Android支持OpenGL ES1.0和OpenGL ES 2.0两种标准. ...
- stm32之GPIO(二)
输入上拉:当IO口作为输入时,比如按键输入,而按键是与地连接,按下时为低电平,则没按下时该IO口应为高电平,上拉即是该IO口通过一个电阻与电源相连,则没按下时为高电平,按下即为低电平. 输入下拉:同理 ...
- 程序中使用事务来管理sql语句的执行,执行失败时,可以达到回滚的要求。
1.设置使用事务的SQL执行语句 /// <summary> /// 使用有事务的SQL语句 /// </summary> /// <param name="s ...