生活中的设计模式之适配器模式
定义
The adapter pattern convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.
将一个类的接口转换成客户期望的另一个接口,使原本不兼容的接口可以一起工作。
小故事
前不久,我开发了一套支付系统,可以支持用户使用积分支付。不久后,新增了微信支付,但微信提供的支付SDK接口和我们系统的Pay接口不一样,所以我便增加if...else语句来扩展新的支付方式。再之后,又增加了支付宝......
public class Client {
public static void main(String[] args) {
if(args[0].equals("积分")){
PointPay pay = new PointPay();
pay.pay(args);
}else if(args[0].equals("微信")){
WXSDKPay wxsdkPay= new WXSDKPay();
wxsdkPay.payByWX(convert(args));
}else if(args[0].equals("支付宝")){
ALISDKPay alisdkPay= new ALISDKPay();
alisdkPay.payByALI(convert(args));
}
}
}
问题
故事中,积分支付、微信支付、支付宝三者的功能都是一样的,但接口却不一样具体地说是操作(行为、方法)不一样。
当两个对象的功能一样,操作不一样时,如果我们使用if...else这种差异化的处理方式操作对象,那么一旦发生扩展就得修改客户端的代码,但如果不允许我们修改客户端代码,又怎么办呢?
因此,为了避免上面的问题,我们应该使用适配器模式——将不一样的操作转换成客户端期望的统一操作。
方案
既然上面"差异化的处理方式"会导致很多问题,那么我们应该统一处理方式。最直接的方式是让适配者(微信支付)继承目标接口(Payment)这样客户就能统一操作这样对象了。
但是,如果我们不能修改适配者呢?那么我们可以新建一个实现目标接口并组合了适配者的类,让它将适配者的操作转换成客户端期望的操作,这个类被称为适配器,这种方式即是适配器模式。
public class WXPayAdapter implements Payment{
protected WXSDKPay wxsdkPay;
public WXPayAdapter(){
wxsdkPay = new WXSDKPay();
}
@Override
public void pay(String[] args) {
//将对象的payByWX操作转换成客户期望的pay操作
wxsdkPay.payByWX(convert(args));
}
}
在适配器模式中,适配器位于客户端和适配者中间,适配者对客户端不可见,因此适配者本身的变化不会影响客户端;适配器的接口和客户端期望的接口一致,因此我们可以在不改变客户端代码的前提下,通过适配器复用已存在的适配者,前提是客户端采用多态访问目标对象。
应用
接下来,我们使用适配器模式重构一下"支付系统",使其可以复用与系统不兼容的对象。
首先,我们的支付系统已经存在一个支付接口以及积分支付类。
//支付接口
public interface Payment {
public void pay(String[] args);
}
//支付接口
public class PointPay implements Payment{
@Override
public void pay(String[] args) {
System.out.println("使用积分支付");
}
}
然后,为了扩展新的支付方式WXSDKPay、ALISDKPay,我们需要为它们创建对应的适配器。
/**微信支付适配器*/
public class WXPaymentAdapter implements Payment {
protected WXSDKPay wxsdkPay;
public WXPaymentAdapter(WXSDKPay wxsdkPay){
this.wxsdkPay = wxsdkPay;
}
@Override
public void pay(String[] args) {
wxsdkPay.payByWX(convert(args));
}
}
/**支付宝适配器*/
public class ALIPaymentAdapter implements Payment {
protected ALISDKPay alisdkPay;
public ALIPaymentAdapter(ALISDKPay alisdkPay){
this.alisdkPay = alisdkPay;
}
@Override
public void pay(String[] args) {
alisdkPay.payByALI(convert(args));
}
}
最后,我们在看看客户端如何使用适配器模式。
public class Client {
public static Payment getPayment(String method){
//这里读者可以理解为动态获取的
PointPay pay = new PointPay();
ALIPaymentAdapter wxAdapter = new ALIPaymentAdapter(new WXSDKPay);
WXPaymentAdapter aliAdapter = new WXPaymentAdapter(new ALISDKPay);
return aliAdapter;
}
public static void main(String[] args) {
//操作方式是统一的
Payment pay = getPayment(args[0]);
pay.pay(args);
}
}
结构
avatar目标接口(Target):声明客户端统一的操作以及适配器需要实现的接口,。
适配者(Adaptee):是客户端期望操作但与目标接口不一致的对象。
适配器(Adapter):实现了目标接口,负责将适配者的操作转成客户期望的操作,它持有适配者。
客户端(Client):负责操作目标接口的对象,它不关心目标接口的实现类是系统提供的还是由适配器包装而成的。
实现类型
对象适配器
对象适配器(Object Adapter)通过组合适配者(Adaptee)和实现目标接口(Target)的方式,给适配者新增目标接口。
avatar
//适配者
public class Adaptee {
public void specificRequest(){}
}
//目标接口
public interface Target {
public void request();
}
//1、实现Target
public class Adapter implements Target{
protected Adaptee adaptee;
//2、组合Adaptee
public Adapter(Adaptee adaptee){
this.adaptee = adaptee;
}
@Override
public void request() {
//3、转换请求
adaptee.specificRequest();
}
}
public class Client {
public void main(String[] args){
//用适配器包装适配者,将其伪装成目标接口
Target target = new Adapter(new Adaptee());
//符合客户的期望
target.request();
}
}
类适配器
类适配器(Class Adapter)和对象适配器主要的差异是,它通过继承的方式而对象适配器通过组合的方式。
//适配者
public class Adaptee {
public void specificRequest(){}
}
//目标接口
public interface Target {
public void request();
}
//1、继承适配者;2、实现目标接口
public class ClassAdapter extends Adaptee implements Target{
@Override
public void leaveRequest() {
//3、转发请求——调用适配器继承下来的方法specificRequest
specificRequest();
}
}
public class Client {
public void main(String[] args){
//客户都不知道有适配者的存在
Target target = new ClassAdapter();
target.leaveRequest();
}
}
总结
当一个对象的接口和系统的接口不一致,而我们又想使用这个对象时,那么我们可以使用适配器将这个对象包装成与系统接口兼容的对象。
这样,可以使我们在不修改系统的前提下,复用已经存在的对象。