各位朋友好,本章节我们继续讲第五个设计模式。

在生活中,我们都知道手机内存卡是无法直接接电脑的,因为内存卡的卡槽比较小,而电脑只有USB插孔,此时我们需要用到读卡器。这个读卡器就相当于是适配器。这是生活上的适配器,那么在OO对象中,适配器就是将一个接口转换成另一个接口,使得客户可以使用。

适配器模式从实现方式上分为两种,类适配器和对象适配器,这两种的区别在于实现方式上的不同,一种采用继承,一种采用组合的方式。

下面我们来看一个例子,下面有两个接口,一个是鹿(Deer),一个是狼(wolf),

public interfaceDeer {public voidrun();public voideatGrass();

}interfaceWolf{public voidrun();public voideatMeat();

}

我们让梅花鹿(SikaDeer)和雪狼(SnowWolf)分别实现这两个接口

class SikaDeer implementsDeer{

@Overridepublic voidrun() {

System.out.println("我在跑");

}

@Overridepublic voideatGrass() {

System.out.println("我在吃草");

}

}class SnowWolf implementsWolf{

@Overridepublic voidrun() {

System.out.println("我在跑");

}

@Overridepublic voideatMeat() {

System.out.println("我在吃肉");

}

}

假设现在狼要吃鹿,但是他要伪装成鹿然后混进去,那么现在因为接口不同,无法伪装,所以现在我们帮它写个适配器:

class SnowAdapter implements Deer{//首先我们需要实现想转换成的接口,也就是鹿要看到的接口

privateSnowWolf snowWolf;publicSnowAdapter(SnowWolf snowWolf) {this.snowWolf = snowWolf;//接着取得我们要适配的对象的引用

}

@Overridepublic voidrun() {

snowWolf.run();//接着实现接口的方法

}

@Overridepublic voideatGrass() {

snowWolf.eatMeat();

}

}

接下来我们写个测试类:

public classTestAdapter {public static voidmain(String[] args) {

Deer sikaDeer= new SikaDeer();//先来个梅花鹿

SnowWolf snowWolf = new SnowWolf();//再来个雪狼

SnowAdapter snowAdapter = new SnowAdapter(snowWolf);//接下来是伪装后的雪狼

System.out.println("snowWolf:");

snowWolf.run();

snowWolf.eatMeat();

System.out.println("sikaDeer says:");

testDeer(sikaDeer);

System.out.println("snowAdapter says:");

testDeer(snowAdapter);

}public static voidtestDeer(Deer deer){

deer.run();

deer.eatGrass();

}

}

结果展示:

在这里,我们来分析一下这个过程,鹿是我们的目标接口,梅花鹿是依据鹿实现的,而适配器实现了目标接口,并持有被适配者(雪狼)的实例。

适配器模式讲一个类的接口,转换成客户期望的另一个。适配器让原本接口不兼容的类可以合作无间。

下面我们看一下类图:

这种实现方式是对象适配器,接下来LZ说一下类适配器

从类图中可以看出,客户想要的是sampleOperation2()方法,但是Adaptee并没有,为了使客户能够使用Adaptee类的sampleOperation2()方法,我们需要提供一个构造器Adapter,把Adaptee的API与Target类的API衔接起来。Adapter与Adaptee是继承关系。

这里所涉及的角色有:

●  目标(Target)角色:这就是所期待得到的接口。注意:由于这里讨论的是类适配器模式,因此目标不可以是类。

●  源(Adapee)角色:现在需要适配的接口。

●  适配器(Adaper)角色:适配器类是本模式的核心。适配器把源接口转换成目标接口。显然,这一角色不可以是接口,而必须是具体类。

我们用代码简单的模仿一下类图的关系,首先是目标角色

public interfaceTarget {/*** 这是源类Adaptee也有的方法*/

public voidsampleOperation1();/*** 这是源类Adapteee没有的方法*/

public voidsampleOperation2();

}

接着是源,

public classAdaptee {public voidsampleOperation1(){}

}

接下来我们写适配器,我们让适配器直接继承源,并实现目标接口,然后补充sampleOperation2()方法

public class Adapter extends Adaptee implementsTarget {@Overridepublic voidsampleOperation2() {//写相关的代码

}

}

类适配器所用的是继承,但是因为JAVA单继承的原因,一个JAVA类只能有一个父类,所以当我们要适配的对象是两个类的时候,我们就无可奈何了。

接下来我们看一下类适配器与对象适配器之间的不同:

①类适配器使用对象继承的方式,是静态的定义方式;而对象适配器使用对象组合的方式,是动态组合的方式。

②对于类适配器,由于适配器直接继承了Adaptee,使得适配器不能和Adaptee的子类一起工作,因为继承是静态的关系,当适配器继承了Adaptee后,就不可能再去处理  Adaptee的子类了。而对于对象适配器,一个适配器可以把多种不同的源适配到同一个目标。换言之,同一个适配器可以把源类和它的子类都适配到目标接口。因为对象适配器采用的是对象组合的关系,只要对象类型正确,是不是子类都无所谓。

③  对于类适配器,适配器可以重定义Adaptee的部分行为,相当于子类覆盖父类的部分实现方法。而对于对象适配器,要重定义Adaptee的行为比较困难,这种情况下,需要定义Adaptee的子类来实现重定义,然后让适配器组合子类。虽然重定义Adaptee的行为比较困难,但是想要增加一些新的行为则方便的很,而且新增加的行为可同时适用于所有的源。

④对于类适配器,仅仅引入了一个对象,并不需要额外的引用来间接得到Adaptee。而 对于对象适配器,需要额外的引用来间接得到Adaptee。

那么到底是多用组合还是多用继承,相信大家经过了前面四章的学习,已经非常明了了。LZ建议尽量使用对象适配器的实现方式,多用合成/聚合、少用继承。不过说到底,具体问题还要进行具体分析才对,根据需要来选用实现方式,最适合的才是最好的。

适配器模式的优点

更好的复用性

系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。

更好的扩展性

在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。

适配器模式的缺点

过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是A接口,其实内部被适配成了B接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构

上面LZ说过适配器从实现方式上分为两种,类适配器和对象适配器,其实从使用目的上来说,也可以分为两种,特殊适配器和缺省适配器,这两种的区别在于使用目的上的不同,一种为了复用原有的代码并适配当前的接口,一种为了提供缺省的实现,避免子类需要实现不该实现的方法。

而我们以上两种方式都是为了复用现有的代码而采用的适配器模式,属于特殊适配器,可称为定制适配器,还有另外一种称为缺省适配器。

下面LZ举一个简单的例子:

interfaceTeacher{voidspeak();voidlisten();voidteach();

}

这是一个老师的接口,张老师要实现它,但是几年前,张老师还只是一个学生,并不会teach(),所以我们需要写一个缺省适配器

abstract class Adapter implementsTeacher{

@Overridepublic voidspeak() {}

@Overridepublic voidlisten() {}

@Overridepublic voidteach() {}

}

然后让张老师继承它

class ZhangTeacher extendsAdapter{

@Overridepublic voidspeak() {

System.out.println("speak");

}

@Overridepublic voidlisten() {

System.out.println("listen");

}

}

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

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

而缺省适配模式可以很好的处理这一情况。可以设计一个抽象的适配器类实现接口,此抽象类要给接口所要求的每一种方法都提供一个空的方法,使它的具体子类免于被迫实现空的方法。

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

这一章讲完,适配器模式相信各位也有了一定的了解,此模式其实就是一种补救措施,在开发的时候,尽量不要用到这个模式。

好了,本次LZ的技术分享就到此结束了,下期分享:状态模式

java 适配器模式详解_JAVA设计模式详解(五)----------适配器模式相关推荐

  1. java合法的数组声明_Java数组详解

    Java数组详解 数组详解 1.数组概述 数组是相同类型数据的有序集合 每一个数据称作一个数组元素,每个数组元素可以通过一个下标来访问它们 2.数组声明和创建 ①数组声明形式 dataType[] a ...

  2. java connection 单例_Java设计模式之单例模式详解

    Java设计模式之单例模式详解 什么是设计模式 设计模式是在大量的实践中总结和理论之后优选的代码结构,编程风格,以及解决问题的思考方式.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可 ...

  3. java 工厂模式详解_Java设计模式之工厂模式详解

    简单工厂其实并不是设计模式,只是一种编程习惯. 首先我们创建父类Cup,所有杯子类的父类.再创建它的子类BigCup和SmallCup类. public abstract class Cup { pu ...

  4. java模型给泛型_java 泛型详解-绝对是对泛型方法讲解最详细的,没有之一

    对java的泛型特性的了解仅限于表面的浅浅一层,直到在学习设计模式时发现有不了解的用法,才想起详细的记录一下. 1. 概述 泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应 ...

  5. java 1.8 泛型_Java 泛型详解

    原标题:Java 泛型详解 引言 泛型是Java中一个非常重要的知识点,在Java集合类框架中泛型被广泛应用.本文我们将从零开始来看一下Java泛型的设计,将会涉及到通配符处理,以及让人苦恼的类型擦除 ...

  6. java泛型常用特点_Java泛型详解

    对java的泛型特性的了解仅限于表面的浅浅一层,直到在学习设计模式时发现有不了解的用法,才想起详细的记录一下. 本文参考java 泛型详解.Java中的泛型方法. java泛型详解 1. 概述 泛型在 ...

  7. java list取值_Java集合详解

    一.集合的由来 通常,我们的程序需要根据程序运行时才知道创建多少个对象.但若非程序运行,程序开发阶段,我们根本不知道到底需要多少个数量的对象,甚至不知道它的准确类型.为了满足这些常规的编程需要,我们要 ...

  8. java 枚举使用例子_Java枚举详解及使用实例(涵盖了所有典型用法)

    在实际编程中,往往存在着这样的"数据集",它们的数值在程序中是稳定的,而且"数据集"中的元素是有限的. 例如星期一到星期日七个数据元素组成了一周的"数 ...

  9. java threadpoolexecutor 返回值_Java ThreadPoolExecutor详解

    ThreadPoolExecutor是Java语言对于线程池的实现.池化技术是一种复用资源,减少开销的技术.线程是操作系统的资源,线程的创建与调度由操作系统负责,线程的创建与调度都要耗费大量的资源,其 ...

  10. java classpath详解_java Classpath全解(转)

    java Classpath全解(转)[@more@]java 的新入门者对classpath往往比较困惑,为何在开发环境中能运行的东东出去就不好,或在外面运行的东东挺溜的进了开发环境就死菜,烦死了! ...

最新文章

  1. python简单装饰器_简单介绍Python装饰器(一)
  2. 关于ospf区域认证以及虚链路之间的配置问题
  3. 题目2:隐式图的搜索问题(A*算法解决八数码)
  4. 二级菜单--竖排---HTML
  5. woocommerce分类页面模板_怎样让wordpress网站的不同分类页面,调用不同的banner图片?...
  6. yii2组件之下拉框带搜索功能(yii-select2)
  7. State(状态)--对象行为型模式
  8. 匿名函数自我调用_Python中的匿名函数及递归思想简析
  9. yum 安装oraclejdk_kubernetes-16:制作oraclejdk镜像
  10. ZigBee网络数据传递流程_Zigbee网络架构解析
  11. php使用file_get_contents 或者curl 发送get/post 请求 的方法总结
  12. 字符串通配符(递归)
  13. 图数据库 Titan 快速入门
  14. 经济学人翻译练习——肯•帕克斯顿的再次竞选是对德克萨斯共和党价值观的考验
  15. mysql数据库表的编辑器,SqliteLobEditor(数据库编辑工具)
  16. dcs与plc与c语言的联系,PLC 与DCS的通讯方式,举例讲解
  17. 8、智慧交通项目(1)
  18. linux系统安全优化策略
  19. 使用Burp Suite对登录页面进行字典攻击
  20. Python 抽取剔除视频帧工具

热门文章

  1. RESTful理解与实践
  2. 爱上调试:div初探,参照物的重要性!
  3. mysql my.cnf文件_详解MySql的配置文件my.cnf
  4. 4. (5.22~6.8)2022年自动化保研信息+分析汇总(夏令营)
  5. 【反思】写在腾讯电话面试之后
  6. Centos7安装加速下载工具aria2
  7. 《浦岛太郎》——太宰治
  8. 北京机动车和自住房摇号查询
  9. Foxmail是否可以隐藏文件夹?【网易企业邮箱申请】
  10. 错误码errno和perror函数