适配器模式
什么叫适配器?
举个简单的例子:我们手机没电了,要去充电了,这时候你发现的“充电头不见了”,然后你开始嚷嚷充电头去哪了。这个“充电头”就是一种适配器,是usb接口到两孔插座的适配器(当然这里不止插头的适配,还有电压、电流的适配),还有那根数据线也可以称为适配器是从Type-C到usb的适配。只不过我们习惯叫它充电头,官方一点就叫做适配器。
1.对象适配器模式
原理:通过组合来实现适配器功能。
当我们要访问的接口A中没有我们想要的方法 ,却在另一个接口B中发现了合适的方法,我们又不能改变访问接口A,在这种情况下,我们可以定义一个适配器p来进行中转,这个适配器p要实现我们访问的接口A,这样我们就能继续访问当前接口A中的方法(虽然它目前不是我们的菜),然后在适配器P中定义私有变量C(对象)(B接口指向变量名),再定义一个带参数的构造器用来为对象C赋值,再在A接口的方法实现中使用对象C调用其来源于B接口的方法。
我们以充电头(电源适配器)为例,它适配的是usb接口与双孔插座。逻辑上来说我们是只能依靠usb接口进行充电,而我们的手机想要充电,只能是以usb头插入usb接口,通过电源适配器的转化来插入双孔插座进行充电。因为你是不可能拿着usb头插入双孔插座进行充电的。
// USB接口 USB接口分为USB2.0和USB3.0
public interface USB {
public int output5V();
}
// 家用插座
public interface HomeSocket {
public int output220V();
}
// 双孔插座 被适配对象
public class DoubleHoleSocket implements HomeSocket {
@Override
public int output220V() {
return 220;
}
}
// 电源适配器
public class PowterAdapter implements USB {
private HomeSocket homeSocket;
public PowterAdapter() {}
public PowterAdapter(HomeSocket homeSocket) {
this.homeSocket = homeSocket;
}
// 适配的操作
public int adapte() {
/*****将220v电压转化为5v电压输出的操作*****/
return homeSocket.output220V() / 44;
}
@Override
public int output5V() {
// 适配
return adapte();
}
}
public class AdapterPatternTest {
public static void main(String[] args) {
// 双孔插座
HomeSocket homeSocket = new DoubleHoleSocket();
// 电源适配器的usb接口
USB usb2 = new PowterAdapter(homeSocket);
// 调用电源适配器的输出
System.out.println("输出电压为:" + usb2.output5V() + "V");
}
}
我们看到的是输出了5V电压,但是这个5V电压却是家用电压220V通过适配而来,而这个适配过程我们并不知道,我们只需调用电源适配器的usb接口就可以得到5V输出电压
2、类适配器模式
原理:通过继承来实现适配器功能。
当我们要访问的接口A中没有我们想要的方法 ,却在另一个接口B中发现了合适的方法,我们又不能改变访问接口A,在这种情况下,我们可以定义一个适配器p来进行中转,这个适配器p要实现我们访问的接口A,这样我们就能继续访问当前接口A中的方法(虽然它目前不是我们的菜),然后再继承接口B的实现类BB,这样我们可以在适配器P中访问接口B的方法了,这时我们在适配器P中的接口A方法中直接引用BB中的合适方法,这样就完成了一个简单的类适配器。
// 适配器 继承被适配对象 实现目标接口
public class PowterAdapter extends DoubleHoleSocket implements USB {
@Override
public int output5V() {
return output220V() / 44;
}
}
public class AdapterPatternTest {
public static void main(String[] args) {
// 双孔插座
HomeSocket homeSocket = new DoubleHoleSocket();
// 电源适配器的usb接口
USB usb2 = new PowterAdapter();
// 调用电源适配器的输出
System.out.println("输出电压为:" + usb2.output5V() + "V");
}
}
到这里,我们比较一下以上两种适配器模式
功能上区别不大,类适配器模式在代码量上要优于对象适配器模式,实现起来较为简单。而在设计模式中我们尽量要多用组合、少用继承。由于对象适配器模式中传的是被适配对象,就算是这个被适配对象的子类的对象调用该适配器也同样可以达到适配的效果,这样就比较灵活。而类适配器模式是直接继承的被适配对象,而被适配对象的子类想要适配,就必须再写一个适配器来用,所以在都允许的情况下,建议使用对象适配器模式
3、接口适配器模式
原理:通过抽象类来实现适配,这种适配稍别于上面所述的适配。
接口适配器使用场景:
想要使用接口中的某个或某些方法,但是接口中有太多方法,我们要使用时必须实现接口并实现其中的所有方法,可以使用抽象类来实现接口,并不对方法进行实现(仅置空),然后我们再继承这个抽象类来通过重写想用的方法的方式来实现。这个抽象类就是适配器。
当存在这样一个接口,其中定义了N多的方法,而我们现在却只想使用其中的一个到几个方法,如果我们直接实现接口,那么我们要对所有的方法进行实现,哪怕我们仅仅是对不需要的方法进行置空(只写一对大括号,不做具体方法实现)也会导致这个类变得臃肿,调用也不方便,这时我们可以使用一个抽象类作为中间件,即适配器,用这个抽象类实现接口,而在抽象类中所有的方法都进行置空,那么我们在创建抽象类的继承类,而且重写我们需要使用的那几个方法即可。
目标接口:A
public interface A {
void a();
void b();
void c();
void d();
void e();
void f();
}
适配器:Adapter
public abstract class Adapter implements A {
public void a(){}
public void b(){}
public void c(){}
public void d(){}
public void e(){}
public void f(){}
}
实现类:Ashili
public class Ashili extends Adapter {
public void a(){
System.out.println("实现A方法被调用");
}
public void d(){
System.out.println("实现d方法被调用");
}
}
测试类:Clienter
public class Clienter {
public static void main(String[] args) {
A a = new Ashili();
a.a();
a.d();
}
}
装饰者模式与对象适配器模式的区别
我们发现装饰者设计模式与对象适配器模式都有一个相同的操作,就是传入一个类对象。看到这里可能会感觉有点类似,的确是这样,但是装饰者模式传入被装饰对象是为了增强被装饰对象的功能(被装饰者的性质没有改变,只是功能增加了),装饰者与被装饰对象可以继承于同一个父类,属于同一类族(如IO里边的InputStream/BufferedInputStream、OutputStream/BufferedOutputStream);对象适配器模式中传入被适配对象,对被适配对象的方法转化成目标对象的方法,从一种接口转化为另一种接口(适配转化之后,性质可能完全改变)。还拿充电头来说,假如此时出现了第二个充电头,而这个充电头较第一个充电头相比,这个充电头具有一些额外的功能,例如:电流保护啊,充满电后立即断电啊……那么这第二个充电头相对于第一个充电头来说就是功能增强了(被装饰了一下),但是这第二个充电头对手机来说仍然是一个适配器,只不过比第一个充电头多一些功能罢了。