本来想开始进行java集合框架的学习,但是看到集合框架中用到了适配器模式,所以先学习一下此设计模式,能让我们对java集合框架的设计思想理解更深入。JDK源码中应用了很多设计模式,在学习源码的遇到的时候会一一进行学习。闲言少叙,开始适配器模式。
适配器模式定义是:适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。其别名为包装器(Wrapper)。 解释适配器很多教材和博客都采用电源适配器这个例子:比如中国和美国的电源插座不匹配,怎么给手机充电呢,这就需要一个适配器,来进行适配。
适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。 结构型模式(Structural Pattern)描述如何将类或者对 象结合在一起形成更大的结构,就像搭积木,可以通过简单积木的组合形成复杂的、功能更为强大的结构。结构型模式可以分为类结构型模式和对象结构型模式:1. 类结构型模式关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系。 2. 对象结构型模式关心类与对象的组合,通过关联关系使得在一 个类中定义另一个类的实例对象,然后通过该对象调用其方法。 根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系(多用组合少用继承),因此大部分结构型模式都是对象结构型模式。
这时候我们可以引出一个原则:对类的功能的扩展,要多用组合,少用继承。对于类的扩展,在面向对象的编程过程中,我们首先想到的是类的继承,由子类继承父类,从而完成了对子类功能的扩展。但是,面向对象的原则告诉我们,对类的功能的扩展要多用组合,而少用继承。详细的介绍可以查看这篇博客:组合还是继承,这是一个问题。此博客举的例子很好,易于理解组合相对于继承的优点。
画出设计模式对于的uml图是学习设计模式的基本要求,面试中也会让我们画出设计模式的uml图,进行解释。
模式所涉及的角色有:
● 目标(Target)角色:这就是所期待得到的接口。注意:由于类适配器模式,因此目标需是接口。
● 源(Adapee)角色:现在需要适配的接口。
● 适配器(Adaper)角色:适配器类是本模式的核心。适配器把源接口转换成目标接口。显然,这一角色不可以是接口,而必须是具体类。
此图Adaper实现了Adapee,同时实现了Target接口。Adapter与Adaptee是继承关系,这决定了这个适配器模式是类的。
冲突:Target期待调用Request方法,而Adaptee并没有(这就是所谓的不兼容了)。
解决方案:为使Target(电脑需要使用的中国的插座接口)能够使用Adaptee(美国提供的插座口)类里的SpecificRequest方法,故提供一个中间环节Adapter(插座转换器)类(继承Adaptee & 实现Target接口),把Adaptee的API与Target的API衔接起来(适配)。这也是精髓,能够使target与adpatee由无关系不适配的情况,转化为可以调用。
我们客户端要使用的是target接口,是期待的接口,而提供实现的是adaptee。
其适配器代码为:
//适配器Adapter继承自Adaptee,同时又实现了目标(Target)接口。
public class Adapter extends Adaptee implements Target {
//目标接口要求调用Request()这个方法名,但源类Adaptee没有方法Request()
//因此适配器补充上这个方法名
//但实际上Request()只是调用源类Adaptee的SpecificRequest()方法的内容
//所以适配器只是将SpecificRequest()方法作了一层封装,封装成Target可以调用的Request()而已
@Override
public void Request() {
this.SpecificRequest();
}
}
客户端(电脑电源线)可以通过下边调用:
public class AdapterPattern {
public static void main(String[] args){
Target mAdapter = new Adapter();
mAdapter.Request(); //这就调用的是adaptee的实现方法
}
}
与类的适配器模式一样,对象的适配器模式把被适配的类的API转换成为目标类的API,与类的适配器模式不同的是,对象的适配器模式不是使用继承关系连接到Adaptee类,而是使用委派关系(组合形式)连接到Adaptee类。
这个包装类包装了一个Adaptee的实例,从而此包装类能够把Adaptee的API与Target类的API衔接起来。Adapter与Adaptee是组合关系,这决定了适配器模式是对象的。
冲突:Target期待调用Request方法,而Adaptee并没有(这就是所谓的不兼容了)。
解决方案:为使Target能够使用Adaptee类里的SpecificRequest方法,故提供一个中间环节Adapter类(包装了一个Adaptee的实例),把Adaptee的API与Target的API衔接起来(适配)。
class Adapter implements Target{
// 直接关联被适配类
private Adaptee adaptee;
// 可以通过构造函数传入具体需要适配的被适配类对象
public Adapter (Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void Request() {
// 这里是使用委托的方式完成特殊功能
this.adaptee.SpecificRequest();
}
}
客户端调用:
public class AdapterPattern {
public static void main(String[] args){
//需要先创建一个被适配类的对象作为参数
Target mAdapter = new Adapter(new Adaptee());
mAdapter.Request();
}
}
类适配器使用对象继承的方式,是静态的定义方式;而对象适配器使用对象组合的方式,是动态组合的方式。建议尽量使用对象适配器的实现方式,多用合成/聚合、少用继承
优点:
复用性:系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。
扩展性:在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。
缺点:
过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是A接口,其实内部被适配成了B接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
缺省适配器模式是适配器模式的一个特例。接口和抽象类结合,产生了优雅的缺省适配模式。当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(或空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求,它适用于一个接口不想使用其所有的方法的情况。因此也称为单接口适配器模式(也叫做缺省适配器模式)。在java集合框架中,有大量的abstract抽象类。就是利用了此设计模式。
(注意上边的uml图实现接口应该用虚线)
适配器模式在很多框架和项目中使用很广泛,比如在jdk源码中,有:
java.util.Arrays#asList()
java.io.InputStreamReader(InputStream) //字符流和字节流的转换
java.io.OutputStreamWriter(OutputStream)
javax.xml.bind.annotation.adapters.XmlAdapter#marshal()
javax.xml.bind.annotation.adapters.XmlAdapter#unmarshal()
都使用了适配器模式,具体可以看适配器模式原理及实例介绍 。介绍了一下应用。阅读源码时,当我们了解了其设计模式,将会理解更加深刻。