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

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

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

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

public interface Deer {
public void run();
public void eatGrass();
} interface Wolf{
public void run();
public void eatMeat();
}

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

class SikaDeer implements Deer{

    @Override
public void run() {
System.out.println("我在跑"); } @Override
public void eatGrass() {
System.out.println("我在吃草"); } } class SnowWolf implements Wolf{ @Override
public void run() {
System.out.println("我在跑"); } @Override
public void eatMeat() {
System.out.println("我在吃肉"); } }

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

class  SnowAdapter implements Deer{//首先我们需要实现想转换成的接口,也就是鹿要看到的接口
private SnowWolf snowWolf;
public SnowAdapter(SnowWolf snowWolf) {
this.snowWolf = snowWolf;//接着取得我们要适配的对象的引用
}
@Override
public void run() {
snowWolf.run();//接着实现接口的方法 } @Override
public void eatGrass() {
snowWolf.eatMeat(); }
}

接下来我们写个测试类:

public class TestAdapter {
public static void main(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 void testDeer(Deer deer){
deer.run();
deer.eatGrass();
}
}

结果展示:

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

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

下面我们看一下类图:

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

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

这里所涉及的角色有:

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

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

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

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

public interface Target {
/**
* 这是源类Adaptee也有的方法
*/
public void sampleOperation1();
/**
* 这是源类Adapteee没有的方法
*/
public void sampleOperation2();
}

接着是源,

public class Adaptee {

    public void sampleOperation1(){}

}

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

public class Adapter extends Adaptee implements Target {

    @Override
public void sampleOperation2() {
//写相关的代码
} }

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

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

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

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

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

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

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

适配器模式的优点

  •   更好的复用性

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

  •   更好的扩展性

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

适配器模式的缺点

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

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

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

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

interface Teacher{
void speak();
void listen();
void teach();
}

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

abstract class Adapter implements  Teacher{

    @Override
public void speak() {} @Override
public void listen() {} @Override
public void teach() {} }

然后让张老师继承它

class ZhangTeacher extends  Adapter{
@Override
public void speak() {
System.out.println("speak");
} @Override
public void listen() {
System.out.println("listen");
}
}

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

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

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

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

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

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

JAVA设计模式详解(五)----------适配器模式的更多相关文章

  1. android java 设计模式详解 Demo

    android java 设计模式详解 最近看了一篇设计模式的文章,深得体会,在此基础我将每种设计模式的案例都写成Demo的形式,方便读者研究学习, 首先先将文章分享给大家: 设计模式(Design ...

  2. JAVA设计模式详解(三)----------装饰者模式

    今天LZ带给大家的是装饰者模式,提起这个设计模式,LZ心里一阵激动,这是LZ学习JAVA以来接触的第一个设计模式,也许也是各位接触的第一个设计模式.记得当初老师在讲IO的时候就提到过它:“是你还有你, ...

  3. JAVA设计模式详解(六)----------状态模式

    各位朋友,本次LZ分享的是状态模式,在这之前,恳请LZ解释一下,由于最近公司事情多,比较忙,所以导致更新速度稍微慢了些(哦,往后LZ会越来越忙=.=). 状态模式,又称状态对象模式(Pattern o ...

  4. JAVA设计模式详解(二)----------观察者模式

    有一个模式可以帮助你的对象知悉现况,不会错过该对象感兴趣的事,对象甚至在运行时可以决定是否要继续被通知,如果一个对象状态的改变需要通知很多对这个对象关注的一系列对象,就可以使用观察者模式 .观察者模式 ...

  5. JAVA设计模式详解(四)----------单例模式

    上一章我们学习了装饰者模式,这章LZ带给大家的是单例模式. 首先单例模式是用来干嘛的?它是用来实例化一个独一无二的对象!那这有什么用处?有一些对象我们只需要一个,比如缓存,线程池等.而事实上,这类对象 ...

  6. JAVA设计模式详解(一)----------策略模式

    策略模式,顾名思义就是设计一个策略算法,然后与对象拆分开来将其单独封装到一系列策略类中,并且它们之间可以相互替换.首先LZ举一个例子为大家引出这一个模式. 例子:某公司的中秋节奖励制度为每个员工发放2 ...

  7. [ 转载 ] Java开发中的23种设计模式详解(转)

    Java开发中的23种设计模式详解(转)   设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类 ...

  8. Java温故而知新(5)设计模式详解(23种)

    一.设计模式的理解 刚开始“不懂”为什么要把很简单的东西搞得那么复杂.后来随着软件开发经验的增加才开始明白我所看到的“复杂”恰恰就是设计模式的精髓所在,我所理解的“简单”就是一把钥匙开一把锁的模式,目 ...

  9. JAVA设计模式简介及六种常见设计模式详解

    一.什么是设计模式                                                                                           ...

随机推荐

  1. [学习笔记]区间dp

    区间 \(dp\) 1.[HAOI2008]玩具取名 \(f[l][r][W/I/N/G]\) 表示区间 \([l,r]\) 中能否压缩成 \(W/I/N/G\) \(Code\ Below:\) # ...

  2. git如何忽略已经加入版本控制的文件

    git移除已经追踪的文件 有时候新增一个文件,会自动追加到git的版本控制当中,但是又不想提交到仓库.可以按照下面的步骤: git status 查看管理状态: ml-py git:(master) ...

  3. iOS开发-仿微信图片分享界面实现

    分享功能目前几乎已成为很多app的标配了,其中微信,微博等app的图片分享界面设计的很棒,不仅能够展示缩略图,还可以预览删除.最近我在做一款社交分享app,其中就要实现图文分享功能,于是试着自行实现仿 ...

  4. IntelliJ IDEA中Debug的使用技巧

    当运行结果跟我们设想的不一致时,我们就可以用debug进行代码调试,下面是我在日常开发中对debug的一些小结 (一)基本介绍 本篇文章是基于IntelliJ IDEA2018.3.2版本,Debug ...

  5. 解释一下核主成分分析(Kernel Principal Component Analysis, KPCA)的公式推导过程(转载)

    KPCA,中文名称”核主成分分析“,是对PCA算法的非线性扩展,言外之意,PCA是线性的,其对于非线性数据往往显得无能为力,例如,不同人之间的人脸图像,肯定存在非线性关系,自己做的基于ORL数据集的实 ...

  6. 剑指offer八之跳台阶

    一.题目 一只青蛙一次可以跳上1级台阶,也可以跳上2级.求该青蛙跳上一个n级的台阶总共有多少种跳法. 二.思路 a.如果两种跳法,1阶或者2阶,那么假定第一次跳的是一阶,那么剩下的是n-1个台阶,跳法 ...

  7. C# 获取所有对象的字符串表示一ToString方法

    应用程序开发过程中经常需要获取对象的字符串表示.Object类中定义了一个ToString的虚方法.所以在任何类型的实例上都能调用该方法. C#中几乎所有的类型都派生自Object,所以如果当前类型没 ...

  8. tensorflow进阶篇-5(反向传播1)

    这里将讲解tensorflow是如何通过计算图来更新变量和最小化损失函数来反向传播误差的:这步将通过声明优化函数来实现.一旦声明好优化函数,tensorflow将通过它在所有的计算图中解决反向传播的项 ...

  9. 【JAVA】枚举

    枚举(enum)类型是Java 5新增的特性,它是一种新的类型,允许用常量来表示特定的数据片断,而且全部都以类型安全的形式来表示. 1.常量的使用 在JDK1.5之前,我们定义常量都是:public ...

  10. koa2 get请求后台正常接收参数 前端报404错误

    刚学习一门技术时,总会踩一些坑. 前端代码 function del(mId){ $.ajax({ type:"get", url:"/delUser", da ...