Java设计模式(1) Adapter(适配器)模式及I/O实例引申
Java设计模式(一) Adapter(适配器)模式及I/O实例引申
基本概念
适配器模式是一种补救模式,将一个类的接口转换成客户希望的另外一个接口。Adapter模式使原本由于接口不兼容而不能一起工作的类可以一起工作。是包装模式(对类或者对象进行包装)的一种,分为类适配器和对象适配器,是从实现层面上划分。
这个模式中包含三种角色:
- Target目标角色:该角色定义我们要将原接口转化为何种接口,也就是我们期望得到的接口(方便多态地使用)
- Adaptee源角色:需要利用适配器进行包装的原接口
- Adapter适配器:该模式的核心角色,具有将Adaptee包装为Target的职责
综上,我们能够知道该模式中各个角色的关系:
Adapter改变了Adaptee的接口,使Adaptee和Adapter的基类Target匹配。这样Client就可以把Adaptee当作Target类型来使用。(多态得以实现)
例讲Adapter的具体实现
类适配器的实现
我们规定一个情景,A公司要收购B公司,但是他们有两套接口不是很一致的人员管理系统,如下:
- 首先我们给出A公司的接口和实现类(其实重要的只是接口)
interface MyEmployee{
public String getName();
public void setName(String name);
public String getPosition();
public void setPosition( String position );
public String getAddress();
public void setAddress( String address );
public String getAge();
public void setAge( String age );
}
实现类如下,给出实现,只是体现适配器能极大程度忽略内部实现细节。
class EmployeeA implements MyEmployee {//实现MyEmployee接口
private String name;
private String position;
private String address;
private String age;
//应该注意@override注释的必要性,能检查是否为有效的覆盖
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
//TODO 同样的getter和setter
}
- 然而我们的B公司的提供了完全不同的接口和实现方式(为了简单直接写在一起了…实际上接口和实现分开是有优势的),B公司将所有的信息存在了一个利用键值对对应的map中,好尴尬啊…适配器就应该在这时候出现进行补救。
class EmployeeB{
private Map<String,String> basicInfo;
public Map<String, String> getBasicInfo() {
return basicInfo;
}
public void setBasicInfo(Map<String, String> basicInfo) {
this.basicInfo = basicInfo;
}
}
我们采用类适配器来解决这一问题,定义上类适配器应该继承原类,实现目标接口,这样能够达到获得源类的方法,并且采用目标接口的调用形式
class EmployeeAdapter extends EmployeeB implements MyEmployee {
@Override
public String getName() {
return getBasicInfo().get("name");
}
@Override
public void setName(String name) {
Map<String,String> dic = getBasicInfo();
String key = "name";
dic.remove( key);
dic.put( key , name );
}
}
然后我们就可以利用MyEmployee接口同时操作两个公司的员工,并且可以实现多态需要达到的任何操作。
public class adapterEg {
public static void main ( String [] args ){
MyEmployee b = new EmployeeAdapter();
MyEmployee a = new EmployeeA();
//TODO 一系列操作
}
}
对象适配器的实现
其实往往现实问题没有我们想象中的那般美好,公司经营出现问题的很多,重组的AB公司开始收购C公司,然而C公司活该倒闭,接口杂乱不堪…..将每个属性独立开来,因为Java不支持多继承(这并不是功能的缺失,而是对好的代码结构的一种保证)
class CName {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
class CAddress{
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
//TODO 诸如此类的重复代码,没必要赘述
我们可以采用委托的方式替代继承的方式,来到适配的效果
class AdapterC implements MyEmployee{
private CName cname;
private CAddress address;
//TODO 其他的过于赘余,不再多讲
AdapterC(){
cname = new CName();
address = new CAddress();
}
@Override
public String getName() {
return cname.getName();
}
@Override
public void setName(String name) {
cname.setName(name);
}
也能够达到适配的效果,是不是很简单!!!!
适配器模式的优点
- 能够让两个设计初期没有考虑公用一个接口的类,能够通过添加适配器补救
- 增加了类的透明性,具体实现委托给了源角色,只提供统一的接口
- 使一些类能够在新的设计中得到复用(可以通过适配解决接口不统一的问题)
- 灵活性好,易拓展的同时也方便删改,因为只需要去掉适配器即可,耦合性低,是良好的设计
适配器模式的适用场景
在系统拓展时,为了使新的设计与旧的设计相适应而采取的模式,它不是为了解决还在处于开发阶段的问题,而是解决正在服役中的项目问题。
同作为包装模式,外观模式(Facade)与适配器模式的区别:
- 在两个模式中都存在既有的类(也就是需要通过包装来适用某些功能的类)
- Facade不需要适用多态行为,而adapter多数情况下是为了适应多态场景,如上述的场景
- 动机上,Facade是为了简化接口,而Adapter尽管也希望接口尽量简单,但是是为了遵循一个已有的接口,即时这样失去了使用一个设计更加简洁的接口的可能。
总结如下表
item | Facade | Adapter |
---|---|---|
是否存在既有的类 | 是 | 是 |
是否要遵循某个接口 | 否 | 是 |
是否适应多态 | 否 | 可能 |
需要更简单的接口 | 是 | 否 |
Java I/O对适配器模式的使用
- 最直接的就是InputStreamReader和OutputStreamWriter就是InputStream和OutputStream针对Reader和Writer接口的适配。
- 比如InputStreamReader实现了Reader接口,并且持有了InputStream的引用(对象适配),通过StreamDecoder类间接持有,因为从byte到char需要编码。
鸡汤
- 技术是为业务服务的,因此业务在日新月异变化的同时,也对技术提出了同样的要求
- 在这种要求下,使用补救模式可以保证我们的系统在生命周期内能够稳定、可靠、健壮的运行,而适配器模式就是这样一个救世主,它通过把非本系统接口的对象包装成本系统可以接收的对象,从而简化了系统大规模变更的风险。
——《设计模式之禅》