概述
适配器(adapter)模式,它的定义是这样的:
适配器模式将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性。
让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)。
核心作用就是适配。这个适配的含义,在于原来的方法不能直接满足需求,进行一定的“处理”后才能使用。
使用这个模式进行设计,需要涉及到几个部分:
- 需要被适配的类或者对象,提供的方法需要被处理才可兼容
- 需要的输出,通常是一个接口,确定适配的输出方法
- 适配器,“处理”被适配者的方法,使其变得兼容,便于使用
以常见的手机充电器来举例,手机充电需要低电压的直流电,而电源输出的则是高电压的交流电,两者是不兼容的。而一般我们使用手机充电器为手机充电就不会有兼容的问题。在这个例子中,电源就是需要被适配的对象,充电器就是一个适配器,充电器输出的低电压直流电就是需要的输出
适配器模式有三种常用的实现方式:
- 类适配器模式
- 对象适配器模式
- 接口适配器模式
类适配器模式
适配器模式的关键在于适配器类“转换”被适配的方法。这样的转换可以被看做是对被转换方法的“复用”。类适配器和对象适配器的差别就是对“复用”实现的区别。
类适配器模式采用继承的方式,让适配器类继承被适配者,从而可以重写被适配者的方法。这是一种白盒复用
我们将刚刚充电器的例子画作类图
给个对应的实现:
import org.junit.Test;
/*场景类*/
public class Client {
@Test
public void phone() {
ChargerOutput output = new Charger();
int voltage = output.output5V();
System.out.println("手机获得充电电压:" + voltage + "V");
}
}
/*适配器*/
public class Charger extends Source implements ChargerOutput {
public int output5V(){
int voltage = output220V();
System.out.println("充电器输出" + voltage / 44 + "V");
return voltage / 44;
}
}
/*适配输出*/
public interface ChargerOutput {
public int output5V();
}
/*被适配的类*/
public class Source {
public int output220V(){
System.out.println("电源输出220V");
return 220;
}
}
结果如下:
刚才说到,类适配器模式是适配器对被适配者内容的白盒复用。因此白盒复用的优缺点也是类适配器的优缺点。
适配器继承了被适配的类,这样会获得所有被继承类的方法,这使得实现适配器的成本会变高,且适配器类中被适配对象和处理逻辑耦合度高,但同时由于适配器可以直接重写被适配类的方法,这样也使得适配器的实现更加灵活。
对象适配器模式
根据合成复用原则,在程序设计时,使用组合的方式复用对象的优先级要高于继承。这在实现适配器模式时也适用。
将上面类适配器中的继承改为组合,将父类变为成员对象,变白盒复用为黑盒复用。
这个成员对象可以在构造时传入,也可以提供setter方法进行更换。
这样一来处理逻辑和源方法就进行了解耦,且不用担心因为继承而带来的重写方法的负担,适配器类只用更加专注于实现适配逻辑即可。
我们将类适配器中的几个类稍作修改:
import org.junit.Test;
/*场景类*/
public class Client {
@Test
public void phone() {
ChargerOutput output = new Charger(new Source());
int voltage = output.output5V();
System.out.println("手机获得充电电压:" + voltage + "V");
}
}
/*电源*/
public class Source {
public int outputVoltage(){
System.out.println("电源输出220V");
return 220;
}
}
/*适配器*/
public class Charger implements ChargerOutput {
private Source src;
public Charger(Source src) {
this.src = src;
}
public void setSource(Source src) {
this.src = src;
}
public int output5V(){
int voltage = src.outputVoltage();
System.out.println("--------------充电器变压--------------------");
System.out.println("电源电压:" + voltage + "V");
System.out.println("输出电压:5V");
System.out.println("变压倍数:" + ((double)voltage / 5));
System.out.println("-----------------------------------------");
return 5;
}
}
/*适配器输出结果*/
public interface ChargerOutput {
public int output5V();
}
由于使用对象适配器使得被适配对象和适配逻辑解耦,这样一来适配器就可以在一定程度上面接受被适配者存在多态。对应到例子中,就是这个充电器接受多种电压输出的电源。下面我们将电源类改造一番,使其派生两个子类,分别输出110V电压和220V电压:
将电源父类改为抽象类:
public abstract class Source {
abstract public int outputVoltage();
}
设计两个电源子类,分别输出220V和110V
public class Source110V extends Source {
@Override
public int outputVoltage() {
// TODO Auto-generated method stub
System.out.println("电源输出电压:110V");
return 110;
}
}
public class Source220V extends Source{
@Override
public int outputVoltage() {
// TODO Auto-generated method stub
System.out.println("电源输出电压:220V");
return 220;
}
}
修改场景方法,使用适配器分别适配两种不同的电源:
public class Client {
@Test
public void phone() {
ChargerOutput output = new Charger(new Source220V());
int voltage = output.output5V();
System.out.println("手机获得充电电压:" + voltage + "V");
System.out.println("\n===============================================\n");
output = new Charger(new Source110V());
voltage = output.output5V();
System.out.println("手机获得充电电压:" + voltage + "V");
}
}
这样一来,程序的灵活性和健壮性就都提升了,简直就是一举两得。
也难怪对象适配器模式是最常用的模式,耦合性低的设计是真的香~~~
接口适配器模式
接口适配器模式,有些地方叫做(Default Adapter Pattern)缺省适配器模式。
当我们的适配器需要有很多种输出或者处理的需求暂时不定时。不方便直接在适配器类中实现所有的适配方法,那么此时最好的做法就是将适配器类做成一个抽象类,给每一个适配方法提供一个默认的实现,当场景中使用到了适配器时,使用匿名类给予需要的适配方法实现即可。
例如,在刚刚的例子中,我们添加一个需求,使得充电器既可以输出5V电压也可以输出10V电压,而具体输出哪一个则由实际使用时再决定。
改写适配输出类,提供多种输出:
public interface ChargerOutput {
/*输出5V电压*/
public int output5V();
/*输出10V电压*/
public int output10V();
}
改写适配器类,将所有方法改为默认实现
public abstract class Charger implements ChargerOutput {
protected Source src;
public Charger(Source src) {
this.src = src;
}
public int output5V(){
return 0;
}
public int output10V() {
return 0;
}
}
接着重写一个场景类,让他们分别输出两种电压:
public class Client {
@Test
public void phone() {
/*重写获取5V电压的方法获取5V电压*/
ChargerOutput output = new Charger(new Source220V()) {
public int output5V() {
int voltage = src.outputVoltage();
System.out.println("--------------充电器变压--------------------");
System.out.println("电源电压:" + voltage + "V");
System.out.println("输出电压:5V");
System.out.println("变压倍数:" + ((double)voltage / 5));
System.out.println("-----------------------------------------");
return 5;
}
};
int voltage = output.output5V();
System.out.println("手机获得充电电压:" + voltage + "V");
System.out.println("\n===============================================\n");
/*重写获取10V电压的方法,获取10V电压*/
output = new Charger(new Source110V()) {
public int output10V() {
int voltage = src.outputVoltage();
System.out.println("--------------充电器变压--------------------");
System.out.println("电源电压:" + voltage + "V");
System.out.println("输出电压:10V");
System.out.println("变压倍数:" + ((double)voltage / 10));
System.out.println("-----------------------------------------");
return 10;
}
};
voltage = output.output10V();
System.out.println("手机获得充电电压:" + voltage + "V");
}
}
结果如下:
需要注意的是,接口适配器模式讨论的问题是输出端口的多样性问题的解决,这和类适配器、对象适配器解决的输入端的问题是不冲突的或者说是并行的。
因此使用接口适配器模式时,适配器的实现可以是类适配器,也可以是对象适配器。
一个有趣的例子
我们说适配器就是将源的输出作为输入进行再加工使结果兼容的一个部件。
借由此,我们可以充分地发挥面向对象抽象和封装的特点,将学生上大学的过程用适配器模式体现出来:
我们可以设定工作岗位有对专业人才的需求,中学培养有综合素质的学生,而大学可作为横在工作和中学之间的适配器,将学生培养成为专业人才。至于适配器的输出,可以简单挑选几个专业。
中学支持多态,提供文科生和理科生,专业暂选四个:理科、工科、医学、人文科学
可以获得如下几个类:
学生类:
public class Student {
private String major; //专业
private String branch; //文理分科
private String expression; //对学业描述
/*学生表述个人信息*/
public String say() {
return "我中学是 " + branch + ",我大学的专业是 " + major + "\n我对学习生涯的看法是:" + expression;
}
}
中学类:
/*中学,培养学生*/
public abstract class AbsMiddleSchool {
abstract public Student train();
}
/*理科学校*/
public class ScienceMiddleSchool extends AbsMiddleSchool{
public Student train() {
Student stu = new Student();
stu.setBranch("理科生");
return stu;
}
}
/*文科学校*/
public class LiberalArtsMeddleSchool extends AbsMiddleSchool{
public Student train() {
Student stu = new Student();
stu.setBranch("文科生");
return stu;
}
}
大学类及输出方法接口
public interface Major {
/*理科学生*/
public Student getScienceStudent();
/*工科学生*/
public Student getEngineerStudent();
/*人文科学学生*/
public Student getHumanitiesStudent();
/*医学生*/
public Student getMedicalStudent();
}
public abstract class University implements Major {
AbsMiddleSchool school;
public University (AbsMiddleSchool school) {
this.school = school;
}
public Student getScienceStudent() {
return null;
}
public Student getEngineerStudent() {
return null;
}
public Student getHumanitiesStudent() {
return null;
}
public Student getMedicalStudent() {
return null;
}
}
使用人才的场景类(以理科专业为例):
import org.junit.Test;
public class Client {
Major major;
@Test
public void Science() {
major = new University(new LiberalArtsMeddleSchool()) {
public Student getScienceStudent() {
Student stu = school.train();
stu.setMajor("理科");
if(stu.getBranch().equals("文科生")) {
stu.setExpression("文科生学理科真是太难了!!");
}else {
stu.setExpression("理科真是妙趣横生");
}
return stu;
}
};
Student stu = major.getScienceStudent();
System.out.println(stu.say());
System.out.println("\n-------------------------------\n");
major = new University(new ScienceMiddleSchool()) {
public Student getScienceStudent() {
Student stu = school.train();
stu.setMajor("理科");
if(stu.getBranch().equals("文科生")) {
stu.setExpression("文科生学理科真是太难了!!");
}else {
stu.setExpression("理科真是妙趣横生!!!!!");
}
return stu;
}
};
stu = major.getScienceStudent();
System.out.println(stu.say());
}
}
最后结果为:
就很nice!!!
设计模式其他文章: