先举个栗子,在生活中我们会常遇到这样的问题,你想要一罐草莓果酱,但是果农呢只能提供给你新鲜好吃的草莓果子,这就不是你想要的呀,于是呀,果农就先把草莓卖到工厂里,果酱工厂对草莓进行加工,加工成果酱之后呢,你再去工厂那里把草莓果酱买回来(忽略中间商店)。那么这个果酱加工厂呢,就算是一个适配器(Adapter),它能帮你把你不是你想要的草莓加工成你想要的草莓果酱。这个过程就是适配器模式。
适配器模式(Adapter)专业一点的定义就是:将一个类的接口转换成客户希望的另外一个接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
其适用场景:
1、已经存在的类的接口不符合我们的需求;2、创建一个可以复用的类,使得该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作;
3、在不对每一个都进行子类化以匹配它们的接口的情况下,使用一些已经存在的子类。
其实现方式主要有两种:
1.类的适配器模式(采用继承实现)
2.对象适配器(采用对象组合方式实现)
3.接口适配器(抽象类来实现)
1、类的适配器
其中:
• Target
— 定义Client使用的与特定领域相关的接口。
• Client
— 与符合Target接口的对象协同。
• Adaptee
— 定义一个已经存在的接口,这个接口需要适配。
• Adapter
— 对Adaptee的接口与Target接口进行适配
在上面的通用类图中,Cient 类最终面对的是 Target 接口(或抽象类),它只能够使用符合这一目标标准的子类;而 Adaptee 类则是被适配的对象(也称 源角色),因为它包含specific (特殊的)操作、功能等,所以我们想要在自己的系统中使用它,将其转换成符合我们标准的类,使得 Client 类可以在透明的情况下任意选择使用 ConcreteTarget 类或是具有特殊功能的 Adaptee 类。
依照上面举的栗子,我们来尝试撸代码~首先是我们需要的草莓果酱(Target):
public interface StrawberryJam {
public void provideStrawberryJam();
}
public class Strawberry {
private static final String NAME = "草莓" ;
public String provideStrawberry(){
return this.NAME;
}
}
public class JamFactory extends Strawberry implements StrawberryJam{
@Override
public void provideStrawberryJam() {
String name = super.provideStrawberry();
name = name + "Jam";
System.out.println("这里我们把草莓加工成草莓果酱:"+ name);
}
}
最后我们看看我们拿到的是什么( Client):
public class Client {
public static void main(String[] args) {
StrawberryJam jam = new JamFactory();
jam.provideStrawberryJam();
}
}
结果:
2、对象的适配器
与类的适配器不同的是,对象的适配器不再使用继成的关系,而采用组合的关系把Adaptee类关联起来。
同样以上面的栗子,我们来怼代码~:
果酱工厂进行改造一下(Adapter):
public class JamFactoryS implements StrawberryJam{
private Strawberry strawberry;
public JamFactoryS(Strawberry strawberry){
this.strawberry = strawberry;
}
@Override
public void provideStrawberryJam() {
String name = strawberry.provideStrawberry();
name += "Jam";
System.out.println("这里我们把草莓加工成草莓果酱:"+ name);
}
}
然后我们再去拿(Client):
public class Client {
public static void main(String[] args) {
StrawberryJam jam = new JamFactoryS(new Strawberry());
jam.provideStrawberryJam();
}
}
看我们拿到的结果:
3、接口适配器
这种适配器模式与前两种略有不同。
当存在这样一个接口,其中定义了N多的方法,而我们现在却只想使用其中的一个到几个方法,如果我们直接实现接口,那么我们要对所有的方法进行实现,哪怕我们仅仅是对不需要的方法进行置空(只写一对大括号,不做具体方法实现)也会导致这个类变得臃肿,调用也不方便,这时我们可以使用一个抽象类作为中间件,即适配器,用这个抽象类实现接口,而在抽象类中所有的方法都进行置空,那么我们在创建抽象类的继承类,而且重写我们需要使用的那几个方法即可。
我们来怼一波代码~:
对于这样一个接口,里面有很多方法。
public interface Target {
void a();
void b();
void c();
void d();
void e();
}
但是我们只想使用其中的a()和d()方法。
那么我们编写适配器(Adapter):
public abstract class Adapter implements Target{
public void a(){};
public void b(){};
public void c(){};
public void d(){};
public void e(){};
}
然后有一个实现类继承这个抽象类:
public class TargetImpl extends Adapter{
public void a(){
System.out.println("这里只需要接口的a()!");
}
public void d(){
System.out.println("这里只需要接口的d()!");
}
}
我们测试一下:
public class Test {
public static void main(String[] args) {
Target target = new TargetImpl();
target.a();
target.d();
}
}
测试结果:
最后总结一下:
这三种方式 前两种实现的背景差不多,所以可以进行比较选择,因为继承只能单继承,所以有多个类需要适配的时候无法使用类的适配器,其次,类的适配器采用继承的方式也增加了耦合性,所以一般建议选择对象的适配器,但是特殊情况需要时选择类的适配器也没有问题。
而接口的适配器使用背景比较单一。没有过多的挑的~~遇到就用吧。