简介

在实际的开发过程中,由于应用环境的变化(例如使用语言的变化),我们需要的实现在新的环境中没有现存对象可以满足,但是其他环境却存在这样现存的对象。那么如果将“将现存的对象”在新的环境中进行调用呢?解决这个问题的办法就是我们本文要介绍的适配器模式——使得新环境中不需要去重复实现已经存在了的实现而很好地把现有对象(指原来环境中的现有对象)加入到新环境来使用。

定义

把一个类的接口变换成客户端所期待的另一种接口,从而使原本接口不匹配而无法一起工作的两个类能够在一起工作。适配器模式有类的适配器模式和对象的适配器模式两种形式,下面我们分别讨论这两种形式的实现和给出对应的类图来帮助大家理清类之间的关系。

类型

结构型模式

适配器模式的结构

类适配器模式和对象适配器模式两种不同的形式。

1. 类适配器模式

类图

图1 类适配器模式类图

2. 对象适配器模式

类图

图2 对象适配器模式类图

角色类

  • 目标角色(Target):定义Client使用的与特定领域相关的接口。
  • 客户角色(Client):与符合Target接口的对象协同。
  • 被适配角色(Adaptee):定义一个已经存在并已经使用的接口,这个接口需要适配。
  • 适配器角色(Adapte) :适配器模式的核心。它将对被适配Adaptee角色已有的接口转换为目标角色Target匹配的接口。对Adaptee的接口与Target接口进行适配.

使用场景

1) 系统需要使用现有的类,而这些类的接口不符合系统的接口。

2) 想要建立一个可以重用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。

3) 两个类所做的事情相同或相似,但是具有不同接口的时候。

4) 旧的系统开发的类已经实现了一些功能,但是客户端却只能以另外接口的形式访问,但我们不希望手动更改原有类的时候。

5) 使用第三方组件,组件接口定义和自己定义的不同,不希望修改自己的接口,但是要使用第三方组件接口的功能。

优点

  • 通过适配器,客户端可以调用同一接口,因而对客户端来说是透明的。这样做更简单、更直接、更紧凑。
  • 复用了现存的类,解决了现存类和复用环境要求不一致的问题。
  • 将目标类和适配者类解耦,通过引入一个适配器类重用现有的适配者类,而无需修改原有代码。
  • 一个对象适配器可以把多个不同的适配者类适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。

缺点

对于对象适配器来说,更换适配器的实现过程比较复杂。

模拟场景

在生活中,我们买的电器插头是2个孔的,但是我们买的插座只有三个孔的,此时我们就希望电器的插头可以转换为三个孔的就好,这样我们就可以直接把它插在插座上,此时三个孔插头就是客户端期待的另一种接口,自然两个孔的插头就是现有的接口。

实现

C#

输出结果:

代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; namespace Adapter
{
class Program
{
static void Main(string[] args)
{
// 现在客户端可以通过适配器使用2个孔的插头了
IThreeHole threehole = new PowerAdapter();
threehole.Request(); // 现在客户端可以通过适配使用2个孔的插头了
ThreeHole_Object threehole_object = new PowerAdapter_Object();
threehole_object.Request();
Console.ReadLine();
}
}
#region 类适配器模式
/// <summary>
/// 三个孔的插头,也就是适配器模式中的目标角色
/// </summary>
public interface IThreeHole
{
void Request();
} /// <summary>
/// 两个孔的插头,源角色——需要适配的类(Adaptee)
/// </summary>
public abstract class TwoHole
{
public void SpecificRequest()
{
Console.WriteLine("我是两孔插头(类适配器)");
}
} /// <summary>
/// 适配器类,接口要放在类的后面
/// 适配器类提供了三个孔插头的行为,但其本质是调用两个孔插头的方法
/// </summary>
public class PowerAdapter : TwoHole, IThreeHole
{
/// <summary>
/// 实现三个孔插头接口方法
/// </summary>
public void Request()
{
// 调用两个孔插头方法
this.SpecificRequest();
}
}
#endregion
#region 对象适配器
/// <summary>
/// 三个孔的插头,也就是适配器模式中的目标(Target)角色
/// </summary>
public class ThreeHole_Object
{
// 客户端需要的方法
public virtual void Request()
{
// 可以把一般实现放在这里
}
} /// <summary>
/// 两个孔的插头,源角色——需要适配的类
/// </summary>
public class TwoHole_Object
{
public void SpecificRequest()
{
Console.WriteLine("我是两孔插头(对象适配器)");
}
} /// <summary>
/// 适配器类,这里适配器类没有TwoHole_Object类,
/// 而是引用了TwoHole_Object对象,所以是对象的适配器模式的实现
/// </summary>
public class PowerAdapter_Object : ThreeHole_Object
{
// 引用两个孔插头的实例,从而将客户端与TwoHole联系起来
public TwoHole_Object twoholeAdaptee = new TwoHole_Object(); /// <summary>
/// 实现三个孔插头接口方法
/// </summary>
public override void Request()
{
twoholeAdaptee.SpecificRequest();
}
}
#endregion
}

Java

输出结果:

代码:

public class Program {
public static void main(String[] args) {
// TODO Auto-generated method stub
// 现在客户端可以通过适配器使用2个孔的插头了
IThreeHole threehole = new PowerAdapter();
threehole.Request(); // 现在客户端可以通过适配使用2个孔的插头了
ThreeHole_Object threehole_object = new PowerAdapter_Object();
threehole_object.Request();
}
}
/**
* 三个孔的插头,也就是适配器模式中的目标角色
*/
interface IThreeHole
{
void Request();
}
/**
* 两个孔的插头,源角色——需要适配的类(Adaptee)
*/
class TwoHole
{
public void SpecificRequest()
{
System.out.println("我是两孔插头(类适配器)");
}
}
/**
* 适配器类,接口要放在类的后面
* 适配器类提供了三个孔插头的行为,但其本质是调用两个孔插头的方法
*/
class PowerAdapter extends TwoHole implements IThreeHole
{
// 实现三个孔插头接口方法
public void Request()
{
// 调用两个孔插头方法
this.SpecificRequest();
}
}
/**
* 三个孔的插头,也就是适配器模式中的目标(Target)角色
*/
class ThreeHole_Object
{
// 客户端需要的方法
public void Request()
{
// 可以把一般实现放在这里
}
}
/**
* 两个孔的插头,源角色——需要适配的类
*/
class TwoHole_Object
{
public void SpecificRequest()
{
System.out.println("我是两孔插头(对象适配器)");
}
}
/**
* 适配器类,这里适配器类没有TwoHole类,
* 而是引用了TwoHole对象,所以是对象的适配器模式的实现
*/
class PowerAdapter_Object extends ThreeHole_Object
{
// 引用两个孔插头的实例,从而将客户端与TwoHole联系起来
public TwoHole_Object twoholeAdaptee = new TwoHole_Object(); // 实现三个孔插头接口方法
public void Request()
{
twoholeAdaptee.SpecificRequest();
}
}

类适配器和对象适配器的权衡

1)类适配器模式:适配器继承自已实现的类(一般多重继承)。

Adapter与Adaptee是继承关系

  • 用一个具体的Adapter类和Target进行匹配。结果是当我们想要一个匹配一个类以及所有它的子类时,类Adapter将不能胜任工作
  • 使得Adapter可以重定义Adaptee的部分行为,因为Adapter是Adaptee的一个子集
  • 仅仅引入一个对象,并不需要额外的指针以间接取得adaptee

2)对象适配器模式:适配器容纳一个它包裹的类的实例。在这种情况下,适配器调用被包裹对象的物理实体。

Adapter与Adaptee是委托关系

  • 允许一个Adapter与多个Adaptee同时工作。Adapter也可以一次给所有的Adaptee添加功能
  • 使用重定义Adaptee的行为比较困难

应用举例

  1. 使用过ADO.NET的开发人员应该都用过DataAdapter,它就是用作DataSet和数据源之间的适配器。DataAdapter通过映射Fill和Update来提供这一适配器。
  2. 最典型的例子就是很多功能手机,每一种机型都自带充电器,有一天自带充电器坏了,万能充电器就是适配器。

适配器模式与其它相关模式

(1)桥梁模式(bridge模式):桥梁模式与对象适配器类似,但是桥梁模式的出发点不同:桥梁模式目的是将接口部分和实现部分分离,从而对它们可以较为容易也相对独立的加以改变。而对象适配器模式则意味着改变一个已有对象的接口

(2)装饰器模式(decorator模式):装饰模式增强了其他对象的功能而同时又不改变它的接口。因此装饰模式对应用的透明性比适配器更好。结果是decorator模式支持递归组合,而纯粹使用适配器是不可能实现这一点的。

(3)外观模式(Facade模式):适配器模式的重点是改变一个单独类的API。Facade的目的是给由许多对象构成的整个子系统,提供更为简洁的接口。而适配器模式就是封装一个单独类,适配器模式经常用在需要第三方API协同工作的场合,设法把你的代码与第三方库隔离开来。

适配器模式与外观模式都是对现相存系统的封装。但这两种模式的意图完全不同,前者使现存系统与正在设计的系统协同工作而后者则为现存系统提供一个更为方便的访问接口。简单地说,适配器模式为事后设计,而外观模式则必须事前设计,因为系统依靠于外观。总之,适配器模式没有引入新的接口,而外观模式则定义了一个全新的接口。

(4)代理模式(Proxy模式):在不改变它的接口的条件下,为另一个对象定义了一个代理。

(5)装饰者模式,适配器模式,外观模式三者之间的区别:

  • 装饰者模式的话,它并不会改变接口,而是将一个一个的接口进行装饰,也就是添加新的功能。
  • 适配器模式是将一个接口通过适配来间接转换为另一个接口。
  • 外观模式的话,其主要是提供一个整洁的一致的接口给客户端。

设计模式之适配器模式(Adapter)(6)的更多相关文章

  1. 乐在其中设计模式(C#) - 适配器模式(Adapter Pattern)

    原文:乐在其中设计模式(C#) - 适配器模式(Adapter Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 适配器模式(Adapter Pattern) 作者:webabc ...

  2. 8.3 GOF设计模式二: 适配器模式 Adapter

    GOF设计模式二: 适配器模式 Adapter  为中国市场生产的电器,到了美国,需要有一个转接器才能使用墙上的插座,这个转接 器的功能.原理?复习单实例模式  SingleTon的三个关键点  ...

  3. 怎样让孩子爱上设计模式 —— 7.适配器模式(Adapter Pattern)

    怎样让孩子爱上设计模式 -- 7.适配器模式(Adapter Pattern) 标签: 设计模式初涉 概念相关 定义: 适配器模式把一个类的接口变换成client所期待的还有一种接口,从而 使原本因接 ...

  4. 二十四种设计模式:适配器模式(Adapter Pattern)

    适配器模式(Adapter Pattern) 介绍将一个类的接口转换成客户希望的另外一个接口.Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作.示例有一个Message实体类 ...

  5. 【设计模式】适配器模式 Adapter Pattern

    适配器模式在软件开发界使用及其广泛,在工业界,现实中也是屡见不鲜.比如手机充电器,笔记本充电器,广播接收器,电视接收器等等.都是适配器. 适配器主要作用是让本来不兼容的两个事物兼容和谐的一起工作.比如 ...

  6. Java设计模式之适配器模式(Adapter)

    转载:<JAVA与模式>之适配器模式 这个总结的挺好的,为了加深印象,我自己再尝试总结一下 1.定义: 适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法 ...

  7. JavaScript设计模式 Item9 --适配器模式Adapter

    适配器模式(转换器面模式),通常是为要使用的接口,不符本应用或本系统使用,而需引入的中间适配层类或对象的情况. 适配器模式的作用是解决两个软件实体间的接口不兼容的问题. 一.定义 适配器模式(Adap ...

  8. python 设计模式之适配器模式 Adapter Class/Object Pattern

    #写在前面 看完了<妙味>和<华医>,又情不自禁的找小说看,点开了推荐里面随机弹出的<暗恋.橘生淮南>,翻了下里面的评论,有个读者从里面摘了一段自己很喜欢的话出来, ...

  9. [设计模式] 7 适配器模式 adapter

    在 Adapter 模式的结构图中可以看到,类模式的 Adapter 采用继承的方式复用 Adaptee的接口,而在对象模式的 Adapter 中我们则采用组合的方式实现 Adaptee 的复用 类模 ...

  10. java设计模式之六适配器模式(Adapter)

    适配器模式将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题.主要分为三类:类的适配器模式.对象的适配器模式.接口的适配器模式.首先,我们来看看类的适配器模 ...

随机推荐

  1. 《mysql必知必会》学习_第17章_20180807_欢

    第17章:组合查询 P114 select vend_id ,prod_id,prod_price from products where prod_price <=5 ; select ven ...

  2. CS231中的python + numpy课程

    本课程中所有作业将使用Python来完成.Python本身就是一种很棒的通用编程语言,现在在一些流行的库(numpy,scipy,matplotlib)的帮助下,它为科学计算提供强大的环境. 我们希望 ...

  3. 如何在 SCSS 使用 JavaScript 变量/scss全局变量

    Update2019/3/6:发现一个更好的方法,预处理器加载一个全局设置文件 官方github给出了详细的配置. 在 SCSS 中使用变量很方便,创建一个 variables.scss 文件,里面声 ...

  4. 关于hermes与solr,es的定位与区别

    Hermes与开源的Solr.ElasticSearch的不同 谈到Hermes的索引技术,相信很多同学都会想到Solr.ElasticSearch.Solr.ElasticSearch在真可谓是大名 ...

  5. IDEA搭建SSM实现登录、注册,数据增删改查功能

     本博文的源代码:百度云盘/java/java实例/SSM实例/SSM实现登录注册,增删改查/IDEA搭建SSM实现登录,注册,增删改查功能.zip 搭建空的Maven项目 使用Intellij id ...

  6. String,StringBuffer与StringBuilder的理解

    String 字符串常量StringBuffer 字符串变量(线程安全)StringBuilder 字符串变量(非线程安全) 简 要的说, String 类型和 StringBuffer 类型的主要性 ...

  7. volatile 关键字了解与使用

    前言 不管是在面试还是实际开发中 volatile 都是一个应该掌握的技能. 首先来看看为什么会出现这个关键字. 内存可见性 由于 Java 内存模型(JMM)规定,所有的变量都存放在主内存中,而每个 ...

  8. 【原创】贡献一个JS的弹出框代码...

    一.前言 最近在做一个项目,自己感觉系统自带的alert()方法的弹出框实在是不堪入目,所以在网上找了一些资料,然后自己加工了一下,做出了自己的一个js弹出框,在这里贡献出来,希望对你有帮助. 二.开 ...

  9. Java并发编程笔记之Semaphore信号量源码分析

    JUC 中 Semaphore 的使用与原理分析,Semaphore 也是 Java 中的一个同步器,与 CountDownLatch 和 CycleBarrier 不同在于它内部的计数器是递增的,那 ...

  10. java反转链表

    /** * 遍历,将当前节点的下一个节点缓存后更改当前节点指针 */ public static Node reverse2(Node head) { if (head == null) return ...