设计模式使用的前提:
适配器模式最好在详细设计阶段不要考虑它,它不是为了解决还处于开发阶段的问题,而是解决正在服役的项目问题,没有一个系统分析师会在做详细设计的时候考虑使用适配器模式,这个模式使用的主要场景是扩展应用中。
注意:项目一定要遵守依赖倒置原则和里氏替换原则,否则即使在适合使用适配器的场合下,也会带来非常大的改造。
程序设计的原则可参考:程序设计原则
一.概要:
1.定义:适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。
2.适配器模式分类:类适配器、对象适配器、接口适配器
3.意图:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
4.主要解决:主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。
5.优点: 1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。 4、灵活性好。
6.缺点: 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。
二.三种适配器模式的详细说明:
1.类适配器:
原理:通过继承来实现适配器功能。
当我们要访问的接口A中没有我们想要的方法 ,却在另一个接口B中发现了合适的方法,我们又不能改变访问接口A,在这种情况下,我们可以定义一个适配器p来进行中转,这个适配器p要实现我们访问的接口A,这样我们就能继续访问当前接口A中的方法(虽然它目前不是我们的菜),然后再继承接口B的实现类BB,这样我们可以在适配器P中访问接口B的方法了,这时我们在适配器P中的接口A方法中直接引用BB中的合适方法,这样就完成了一个简单的类适配器。
下面使用员工的信息为例:
需求更改前,员工信息的接口:
public interface IStaff {
String getName();
int getAge();
}
需求更改前,员工信息的实现:
public class Staff implements IStaff {
private String name;
private int age;
@Override
public String getName() {
return name;
}
@Override
public int getAge() {
return age;
}
}
需求更改后,新增员工合同时间信息的接口:
public interface IContract {
int getContractTime();
}
需求更改后,新增员工合同时间信息的实现:
public class Contract implements IContract {
private int contractTime;
@Override
public int getContractTime() {
return contractTime;
}
}
需求更改后,新增合同员工信息的实现:
public class ContractStaff extends Contract implements IStaff {
private String name;
private int age;
@Override
public String getName() {
return name;
}
@Override
public int getAge() {
return age;
}
}
测试代码:
public static void main(String args[]) {
/*** 需要更改前,员工信息获取 ***/
IStaff staff = new Staff();
staff.getName();
staff.getAge();
/*** 需要更改后,新增的合同员工的信息获取 ***/
IStaff contractStaff = new ContractStaff();
contractStaff.getName();
contractStaff.getAge();
/*** 新增信息:获取合同员工合同时间 ***/
((ContractStaff) contractStaff).getContractTime();
}
2.对象适配器模式:
原理:通过组合来实现适配器功能。
当我们要访问的接口A中没有我们想要的方法 ,却在另一个接口B中发现了合适的方法,我们又不能改变访问接口A,在这种情况下,我们可以定义一个适配器p来进行中转,这个适配器p要实现我们访问的接口A,这样我们就能继续访问当前接口A中的方法(虽然它目前不是我们的菜),然后在适配器P中定义私有变量C(对象)(B接口指向变量名),再定义一个带参数的构造器用来为对象C赋值,再在A接口的方法实现中使用对象C调用其来源于B接口的方法。
举个例子:220v机器需要适配110v电压
110v电压接口:
public interface IV110 {
int output();
}
110v电压实现:
public class V110 implements IV110 {
@Override
public int output() {
return 110;
}
}
220v电压接口:
public interface IV220 {
int output();
}
220v电压实现:
public class V220 implements IV220 {
@Override
public int output() {
return 220;
}
}
220v机器的接口:
public interface IMachine220V {
boolean is220V();
}
220v机器的实现:
public class Machine220V implements IMachine220V {
private int v;
public Machine220V(int v){
this.v = v;
}
@Override
public boolean is220V() {
return v == 220;
}
}
110v转220v适配器:
public class Adapter implements IV220{
private IV110 iv110;
public Adapter(IV110 iv110) {
this.iv110 = iv110;
}
@Override
public int output() {
return iv110.output() * 2;
}
}
测试:
public static void main(String args[]){
/*** 正常情况下:220v机器使用220v电压 ***/
V220 v220 = new V220();
Machine220V machine1 = new Machine220V(v220.output());
System.out.println("machine1 : " + machine1.is220V());
/*** 电压不同的情况下:220v机器使用110v电压 ***/
V110 v110 = new V110();
Adapter adapter = new Adapter(v110);
Machine220V machine2 = new Machine220V(adapter.output());
System.out.println("machine2 : " + machine2.is220V());
}
输出:
machine1 : true
machine2 : true
3.接口适配器:
原理:通过抽象类来实现适配,这种适配稍别于上面所述的适配。
当存在这样一个接口,其中定义了N多的方法,而我们现在却只想使用其中的一个到几个方法,如果我们直接实现接口,那么我们要对所有的方法进行实现,哪怕我们仅仅是对不需要的方法进行置空(只写一对大括号,不做具体方法实现)也会导致这个类变得臃肿,调用也不方便,这时我们可以使用一个抽象类作为中间件,即适配器,用这个抽象类实现接口,而在抽象类中所有的方法都进行置空,那么我们在创建抽象类的继承类,而且重写我们需要使用的那几个方法即可。
目标接口:
public interface IWorkMethod {
void method_1();
void method_2();
void method_3();
void method_4();
void method_5();
}
适配器:
public abstract class WorkMethodAdapter implements IWorkMethod {
public void method_1(){}
public void method_2(){}
public void method_3(){}
public void method_4(){}
public void method_5(){}
}
实现类:
public class AWorkMethodAdapter extends WorkMethodAdapter {
public void method_1(){
System.out.println("work with method1");
}
public void method_2(){
System.out.println("work with method2");
}
}
测试:
public static void main(String args[]){
AWorkMethodAdapter aWorkMethod = new AWorkMethodAdapter();
aWorkMethod.method_1();
aWorkMethod.method_2();
}
三.适配器的使用场景:
类适配器与对象适配器的使用场景一致,仅仅是实现手段稍有区别,二者主要用于如下场景:
(1)想要使用一个已经存在的类,但是它却不符合现有的接口规范,导致无法直接去访问,这时创建一个适配器就能间接去访问这个类中的方法。
(2)我们有一个类,想将其设计为可重用的类(可被多处访问),我们可以创建适配器来将这个类来适配其他没有提供合适接口的类。
以上两个场景其实就是从两个角度来描述一类问题,那就是要访问的方法不在合适的接口里,一个从接口出发(被访问),一个从访问出发(主动访问)。
接口适配器使用场景:
(1)想要使用接口中的某个或某些方法,但是接口中有太多方法,我们要使用时必须实现接口并实现其中的所有方法,可以使用抽象类来实现接口,并不对方法进行实现(仅置空),然后我们再继承这个抽象类来通过重写想用的方法的方式来实现。这个抽象类就是适配器。