适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。

  适配器模式的一些其他名称:变压器模式、转换器模式、包装(Wrapper)模式。适配器模式可以用于增加新的方法,但是,其主要意图是转换接口。

1.  现实生活中的适配器模式

  连接口径不一致的两根水管问题。假设两根水管,一根粗一根细,为了将这两根水管连接到一起,就需要用一个连接器将两个管子连接起来,如下:

    

2.适配器模式与概念

  适配器模式有类的适配器和对象的适配器模式。如下图:

    

2.1  类的适配器模式

  类的适配器模式把适配的类的API转换成为目标类的API,其静态结构如下:

    

  从图中看出,Adaptee没有option()方法,而客户端期待这个方法。因此采用一个适配器Adapter,继承Adaptee并且实现Target接口,在Adapter中实现option方法,并且重用Adaptee的option2方法。由于Adapter继承Adaptee,所以为类适配器模式。

角色有:

目标(Target)角色:期待的接口,由于是类适配器模式,因此目标不可以是类。

源(Adaptee)角色:现有需要适配的接口。

适配器(Adapter)角色:适配器是本模式的核心。把原接口转换成目标接口。这一角色必须是类,不可以是接口。

代码如下:

package cn.qlq.adapter;

public interface Target {

    void option();

    void option2();
}
package cn.qlq.adapter;

public class Adaptee {

    public void option2() {
System.out.println("option2");
}
}
package cn.qlq.adapter;

public class Adapter extends Adaptee implements Target {

    @Override
public void option() {
System.out.println("option");
} }

效果:使用一个具体类(Adapter)把源接口适配到目标(Target)中。这一依赖,如果源以及源的子类都使用此类适配就行不通了。

  由于适配器类是源的子类,因此可以在适配器类覆盖源的一些方法。

2.2 对象的适配器模式

  与类适配器不同的是,此模式不使用继承,而是使用委派关系连接到Adaptee类。其结构图如下:

  

  从图中看出,Adaptee没有option()方法,而客户端期待这个方法。因此采用一个适配器Adapter,实现Target接口。Adapter内部将option2任务委派给Adaptee,自己实现option方法。

角色有:

目标(Target)角色:期待的接口,目标可以是具体或抽象的类。

源(Adaptee)角色:现有需要适配的接口。

适配器(Adapter)角色:适配器是本模式的核心。把原接口转换成目标接口。这一角色必须是类,不可以是接口。

代码如下:

package cn.qlq.adapter;

public interface Target {

    void option();

    void option2();
}
package cn.qlq.adapter;

public class Adaptee {

    public void option2() {
System.out.println("option2");
}
}
package cn.qlq.adapter;

public class Adapter implements Target {

    private Adaptee adaptee;

    public Adapter(Adaptee adaptee) {
super();
this.adaptee = adaptee;
} @Override
public void option() {
System.out.println("option");
} @Override
public void option2() {
adaptee.option2();
} }

效果:

(1)一个适配器可以把不同的源适配到同一个Target,换言之,同一个适配器可以把源类以及它的子类都适配到目标接口。

(2)与类的适配器模式相比,想要更换源类的方法就不容易。增加一些新的方法很容易。

2.3  类适配器模式与对象适配器模式区别

  对象适配器模式可以把多种不同的源适配到同一个Target,而类的适配器模式很难实现。

  如果一个被适配源类有大量的方法,使用类适配器模式比价容易(只需要Adapter类继承Adaptee即可)。 

2.4  适配器模式适用场景:

1.  系统需要使用现有的类,而此类的接口不符合系统的需要。

2.  想要建立一些可以重复使用的类,使得本来接口不相容并且无关的类结合在一起工作。

3.  在设计中需要改变多个子类接口,在作用相同但名称不同的类或方法之间进行适配时。

3.   缺省适配器模式

  缺省适配(Default Adapter)模式为一个接口提供缺省实现,这样子类型可以从这个缺省实现进行扩展,而不必从原有接口进行扩展。作为适配器模式的一个特例,缺省适配模式在JAVA语言中有着特殊的应用。

  其实主要就是为Target提供一个默认的实现方法(该实现一般为abstract类,因为其实例化是没有意义的,用于在子类中扩展所需方法)。(JDK8之前不允许接口有实现方法,JDK8可以用default关键字声明方法)。

鲁智深的故事

  和尚要做什么呢?吃斋、念经、打坐、撞钟、习武等。如果设计一个和尚接口,给出所有的和尚都需要实现的方法,那么这个接口应当如下:

public interface 和尚 {
public void 吃斋(); public void 念经(); public void 打坐(); public void 撞钟(); public void 习武(); public String getName();
}

  显然,所有的和尚类都应当实现接口所定义的全部方法,不然就根本通不过JAVA语言编辑器。像下面的鲁智深类就不行。

public class 鲁智深 implements 和尚{
public void 习武(){
}
public String getName(){
return "鲁智深";
}
}

   由于鲁智深只实现了getName()和习武()方法,而没有实现任何其他的方法。因此,它根本就通不过Java语言编译器。鲁智深类只有实现和尚接口的所有的方法才可以通过Java语言编译器,但是这样一来鲁智深就不再是鲁智深了。以史为鉴,可以知天下。研究一下几百年前鲁智深是怎么剃度成和尚的,会对Java编程有很大的启发。不错,当初鲁达剃度,众僧说:“此人形容丑恶、相貌凶顽,不可剃度他",但是长老却说:”此人上应天星、心地刚直。虽然时下凶顽,命中驳杂,久后却得清净。证果非凡,汝等皆不及他。”

  原来如此!看来只要这里也应上一个天星的话,问题就解决了!使用面向对象的语言来说,“应”者,实现也;“天星”者,抽象类也。

public abstract class 天星 implements 和尚 {

    @Override
public void 吃斋() {
} @Override
public void 念经() {
} @Override
public void 打坐() {
} @Override
public void 撞钟() {
} @Override
public void 习武() {
} @Override
public String getName() {
return null;
} }

  鲁智深类继承抽象类“天星”

public class 鲁智深 extends 天星 {

    public void 习武() {
System.out.println("少林金刚圈");
} public String getName() {
return "鲁智深";
} }

  这个抽象的天星类便是一个适配器类,鲁智深实际上借助于适配器模式达到了剃度的目的。此适配器类实现了和尚接口所要求的所有方法。但是与通常的适配器模式不同的是,此适配器类给出的所有的方法的实现都是“平庸”的(也就是都是空方法,没有任何业务相关代码)。这种“平庸化”的适配器模式称作缺省适配模式。

  其类图如下:

  在很多情况下,必须让一个具体类实现某一个接口,但是这个类又用不到接口所规定的所有的方法。通常的处理方法是,这个具体类要实现所有的方法,那些有用的方法要有实现,那些没有用的方法也要有空的、平庸的实现。

  这些空的方法是一种浪费,有时也是一种混乱。除非看过这些空方法的代码,程序员可能会以为这些方法不是空的。即便他知道其中有一些方法是空的,也不一定知道哪些方法是空的,哪些方法不是空的,除非看过这些方法的源代码或是文档。

  缺省适配模式可以很好的处理这一情况。可以设计一个抽象的适配器类实现接口,此抽象类要给接口所要求的每一种方法都提供一个空的方法。就像帮助了鲁智深的“上应天星”一样,此抽象类可以使它的具体子类免于被迫实现空的方法。

  缺省适配器模式的核心是一个缺省适配器类,这个类应该是抽象类,因为这个类不应该被实例化,其实例化也是没意义的。但是抽象类的方法应该是具体的方法,而不是抽象方法,这些方法的存在就是为了提供默认的实现,以便缺省适配器的具体子类可以按需扩展相应方法。

  在任何时候,如果不准备实现一个接口的所有方法时,就可以使用“缺省适配模式”制造一个抽象类,给出所有方法的平庸(也就是空方法)的具体实现。这样,从这个抽象类再继承下去的子类就不必实现所有的方法了。

适配器(Adapter)模式的更多相关文章

  1. 设计模式--适配器(Adapter)模式

    今天学习另一个设计模式,适配器(Adapter)模式,这是一个共同方向,但有特殊要求,就应用到此设计模式.写到这里,想起很久以前,有写过一篇<ASP.NET的适配器设计模式(Adapter)&g ...

  2. 【原】模式之-适配器Adapter模式

    适配器Adapter模式 适配器模式(Adapter Pattern)把一个类的接口变换成客户端所期待的的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作. 模式所涉及的角色有 ...

  3. java演示适配器(adapter)模式

    为什么要使用模式: 模式是一种做事的一种方法,也即实现某个目标的途径,或者技术. adapter模式的宗旨就是,保留现有类所提供的服务,向客户提供接口,以满足客户的需求. 类适配器:客户端定义了接口并 ...

  4. Java 实现适配器(Adapter)模式

    平时我们会常常碰到这种情况,有了两个现成的类,它们之间没有什么联系.可是我们如今既想用当中一个类的方法.同一时候也想用另外一个类的方法.有一个解决方法是.改动它们各自的接口.可是这是我们最不愿意看到的 ...

  5. 设计模式C++描述----06.适配器(Adapter)模式

    一. 定义 适配器模式将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作. Adapter 模式的两种类别:类模式和对象模式. 二. 举例说明 实际中 ...

  6. 2、适配器 adapter 模式 加个"适配器" 以便于复用 结构型设计模式

    1.什么是适配器模式? 适配器如同一个常见的变压器,也如同电脑的变压器和插线板之间的电源连接线,他们虽然都是3相的,但是电脑后面的插孔却不能直接插到插线板上. 如果想让额定工作电压是直流12伏特的笔记 ...

  7. 漫谈设计模式(一):代理(Proxy)模式与适配器(Adapter)模式对比

    1.前言 为什么要将代理模式与适配器模式放在一起来说呢?因为它们有许多的共同点,当然也有一些不同的地方.首先两者都是属于结构型模式.结构型模型是这样定义的: 结构型模式涉及到如何组合类和类以获得更大的 ...

  8. Adapter(适配器)模式

    1. 概述: 接口的改变,是一个需要程序员们必须(虽然很不情愿)接受和处理的普遍问题.程序提供者们修改他们的代码;系统库被修正;各种程序语言以及相关库的发展和进化.  例子1:iphone4,你即可以 ...

  9. 设计模式模式适配器(Adapter)摘录

    23种子GOF设计模式一般分为三类:创建模式.结构模型.行为模式. 创建模式抽象的实例,他们帮助建立一个系统,是独立于如何.这是一个这些对象和陈述的组合.创建使用继承一个类架构更改实例,一个对象类型模 ...

随机推荐

  1. 利用Python读取图片exif敏感信息

    众所周知,现在很多的照相机等软件,拍摄会有选项,是否包含位置信息等. 当然有的人会说,我在微信中查看图片exif信息并没有啊,这是因为你发送到微信服务器的时候,微信帮你完成了保密工作. 常见的图片中包 ...

  2. 3 CVE-2017-11882漏洞分析

    CVE-2017-11882漏洞分析 操作系统:Windows7 32/64位 专业版.Linux 软件:office 2003 sp3 工具:OD.IDA.Python模块.msfconsole 1 ...

  3. Kaldi语音识别快速入门

    一.简介 Kaldi是使用C++编写的语音识别工具包,Apache License v2.0许可.主要供语音识别研究人员使用.Kaldi的目标和范围与HTK类似.目标是拥有易于修改和扩展的现代而灵活的 ...

  4. (原)堆叠hourglass网络

    转载请注明出处: https://www.cnblogs.com/darkknightzh/p/11486185.html 论文: https://arxiv.org/abs/1603.06937 官 ...

  5. nmap指令

    -sP  主机发现    -p  端口扫描(可区域)  -sV  端口(服务版本信息)-O  操作系统-iL  使用列表里的IP.(快捷方便)-iR  对公网上的随机n个IP.--excludeile ...

  6. shell 脚本命令之alias

    1.alias的功能 设置一个别名,即为一个长命令起一个新的名字 2.alias的基本格式 alias   alias_name='origin_command' alias是指定别名命令的关键字 a ...

  7. Anaconda 安装 tensorflow 和 keras

    说明:此操作是在 Anaconda Prompt 窗口完成的 CPU版 tensorflow 的安装. 1.用 conda 创建虚拟环境 tensorflow python=3.6 conda cre ...

  8. tensorflow运行时错误:服务似乎挂掉了,但是会立刻重启的.

    以前在POD里跑起来,没问题的示例代码. 移到jupyter中,多给两个GPU,有时运行就会爆出这个错误: 于是,按网上的意见,暂时加了个使用GPU的指定, 暂时搞定. 如下红色部分. import ...

  9. POJ 2516Minimum Cost(最小费用流+特判)

    [题意]: 有N个人,M个仓库,每个人需要物品,个数都等于共同的K,仓库中有对应的K件物品的数量,随后给K个N*M矩阵(小写k, n, m表示K,N,M对应的子集),表明m个仓库到第n个人的位置运送k ...

  10. python笔试题

    冒泡排序的原理:每次对相邻的两个元素进行比较,若前者大于后者,这将两者的位置交换.第一轮就可以将最大的元素置于列表的最后.几轮循环 冒泡排序的前提条件:有序的列表 import unittest# 冒 ...