Java设计模式-3种适配器模式详解

Java设计模式-3种适配器模式详解


此文为课后笔记,视频资源: 尚硅谷图解Java设计模式韩顺平老师2019力作
3种适配器模式:类适配器模式,对象适配器模式,接口适配器模式

0.前言

讲到适配器,我最先想到的就是电源适配器(俗称充电头),以手机为例,手机不能直接接受220V的交流电,需要通过电源适配器处理一下,转换成手机能接受的电压(比如直流电压5V)。适配器模式中的适配器也是干这种事的,解决两个类不兼容的问题,加一些中间处理操作,使得他们兼容。

适配器模式(AdapterPattern):将一个类的接口(src原类/原接口)转换成客户希望的另外一个接口(dst目标类),使得原本由于接口不兼容而不能一起工作的那些类能一起工作。

适配器模式又分为类适配器模式,对象适配器模式,接口适配器模式三种。他们的区别只是src原类在适配器类种出现的方式不同

下面的代码将模拟这样的场景:220V交流电——>适配器——>5V直流电

具体到代码即:源电压类,适配器类,5V直流电接口,手机类,测试类

其中源电压类,适配器类都是一样的先贴出来:

源电压类(被适配的类):

/**
 * 适配器模式——源类
 * 被适配的类
 * 220V交流电
 */
public class VoltageSrc {
    private int voltageSize=220;
    private String voltageType="交流";
    //输出源电压 220v
    public int outputVoltageSize(){
        System.out.println("源电压 输出大小为 "+voltageSize+" V");
        return voltageSize;
    }
    //输出源电压 220v
    public String outputVoltageType(){
        System.out.println("源电压 输出类型为"+voltageType);
        return voltageType;
    }
}

适配后的结果(目标接口类)

/**
 * 目标接口
 * 将来手机要使用的
 */
public interface IVoltageDst {
    //输出目标电压 大小 5v
     int outputVoltageDstSize();

    //输出目标电压 类型 直流
     String outputVoltageDstType();
}

1.类适配器

1.1类适配器简介

在类适配器中,Src源类(被适配的类)以类的形式出现在适配器中,即源类是适配器的父类。

所以适配器类需要继承src源类,实现dst目标接口:实现目标接口的方法,在方法中通过调用父类方法以及转换操作完成适配。

1.2 类适配器模式中的角色

  • Target目标接口:目标接口类,定义最终需要的方法,在该例中即IVoltageDst接口
  • Adaptee被适配者:被适配的类(src源类),持有既定的变量与在方法,该例子中即VoltageSrct类
  • Adapter适配器:适配器类,将被适配者中的既定变量与方法转换成目标接口适合的,即本例中ClassAdapter类。
  • Client请求者/使用者:请求类,即本例中的Phone手机类

1.3 类适配器模式UML类图

类适配器模式UML类图

1.4 代码实现

该例子中适配器类代码如下:


/**
 * 适配器模式
 * 1.类适配器  继承src源类 实现dst目标接口
 */
public class ClassAdapter extends VoltageSrc implements IVoltageDst {
    @Override
    public int outputVoltageDstSize() {
        //首先获取源电压 大小 类型
        int voltageSize = outputVoltageSize();//使用父类的方法 获取源电压大小
        //模拟源电压到目标电压的转换过程 电压大小转换
        return voltageSize / 44;//硬核转换,意思一下
    }

    @Override
    public String outputVoltageDstType() {
        String voltageType = outputVoltageType();//使用父类的方法 获取源电压类型
        //模拟源电压到目标电压的转换过程 电压类型转换
        if (voltageType.equals("交流")){
            voltageType = "直流";//硬核转换,意思一下
        }
        return voltageType;
    }
}

手机类:

编写充电方法,传入的参数为目标接口的实例,完成充电操作(先判断电压大小和类型)

public class Phone {
    //充电
    public void charging(IVoltageDst iVoltageDst){
        if (iVoltageDst.outputVoltageDstSize()==5
                && iVoltageDst.outputVoltageDstType().equals("直流")){
            System.out.println("电压适配成功,手机冲电ing...");
        }else {
            System.out.println("电压适配失败,手机充不了电2333");
        }
    }
}

1.5 测试编码与分析

创建一个手机类的对象,调用充电方法,传入的参数为适配器类的对象

public class Test {
    public static void main(String[] args) {
            //类适配器测试
            System.out.println("适配器模式 1.类适配器测试");
            Phone phone=new Phone();
            phone.charging(new ClassAdapter());//传入适配器实例
    }
}

测试结果

适配器模式 1.类适配器测试
源电压 输出大小为 220 V
源电压 输出类型为直流
电压适配成功,手机冲电ing...

结果分析:

  • 因为适配器完成了目标接口的实例化(implements IVoltageDst),所以适配器实例可以作为参数传入手机类Phone的充电方法charging()。

  • 因为适配器完成了源电压到目标电压的转换(220V交流电—>5V直流电),所以充电可以正常进行。

1.6 阶段小结

缺点:

  • 由于需要继承src源类(被适配的类),加上Java单继承机制,导致适配目标只能为接口。
  • src源类(被适配的类)中的方法都会在适配器类中暴露

优点:

  • 由于继承自src源类(被适配的类),所以可重写该类中的方法,增加适配器的灵活性

2.对象适配器

2.1 简介

对象适配器基本于类适配器一致,区别是:

在类适配器中,Src源类(被适配的类)以类的形式出现在适配器中,即源类是适配器的父类。

在对象适配器中,持有src源类(被适配的类),即src源类作为适配器的属性出现。

所以适配器类只需要实现dst目标接口:实现目标接口的方法,在方法中通过调用持有src源类实例的方法以及转换操作完成适配。

2.2 对象适配器UML类图

对象适配器中的角色与类适配器中的一致,不重复写了。

UML类图略有改变,如下所示:
对象适配器UML类图

2.3 代码实现

需要修改适配器类:

/**
 * 适配器模式
 * 2.对象适配器
 *
 */
public class ObjectAdapter implements IVoltageDst {
    VoltageSrc voltageSrc;//持有被适配类的对象 聚合关系

    //构造方法,传入被适配类的对象
    public ObjectAdapter(VoltageSrc voltageSrc) {
        this.voltageSrc = voltageSrc;
    }

    @Override
    public int outputVoltageDstSize() {
        return 0;
    }

    @Override
    public String outputVoltageDstType() {
        return null;
    }
}

2.4 测试编码与分析

测试类也需要做些微调,Test类修改如下:

public class Test {
    public static void main(String[] args) {
        //类适配器测试
        System.out.println("适配器模式 1.类适配器测试");
        Phone phone = new Phone();
        phone.charging(new ObjectAdapter(new VoltageSrc()));//传入适配器,其构造函数传入被适配类的实例
    }
}

测试结果

适配器模式 2.对象适配器测试
源电压 输出大小为 220 V
适配电压大小完成,适配完后电压大小:5 V 
源电压 输出类型为交流
适配电压类型完成,适配完后电压类型:直流
电压适配成功,手机冲电ing...

2.5 阶段小结

对象适配器与类适配器思想一致,只是实现方式由继承src源类(被适配的类)改为持有,利用到了合成复用原则(组合代替继承),所以解决了类适配器模式中适配器类必须继承Src源类的局限,而且不再要求dst(最终适配目标)必须是接口。

3.接口适配器

3.1 接口适配器简介

接口适配器模式也称缺省适配器模式,适用于一个类想使用一个接口中的部分方法,适配后只需实现需使用的方法即可。

3.2 接口适配器实现原理

根据上述简介的描述,如何实现的适配呢?其实就是设计一个抽象类实现原接口,每个方法都默认实现(即空实现),该抽象类就是接口适配器类。之后若只需使用原接口中的部分方法,则继承接口适配器类,便可选择性的实现所需要的方法。

下面以一个稍微具体一点的例子展现接口适配器:

原接口(被适配的接口)有4个方法:methodA(),methodB(),methodC(),methodD()

接口请求类/使用类1(ClientA)只需要使用原接口中的methodA(),methodC()

接口请求类/使用类2(ClientB)只需要使用原接口中的methodB(),methodC()

如果不使用接口适配器的话,代码如下:

原接口(被适配的接口):

/**
 * 原接口 被适配的接口类
 */
public interface ISrc {
    void methodA();
    void methodB();
    void methodC();
    void methodD();
}

接口请求类/使用类1(ClientA):

/**
 * 接口请求类/使用类 A
 *
 * 只需要使用原接口中的methodA(),methodC()
 */
public class ClientA implements ISrc {
    @Override
    public void methodA() {
        //需要使用,重写后进行需要的操作
    }
    @Override
    public void methodB() {
        //需要使用,重写后进行需要的操作
    }
    @Override
    public void methodC() {
        //不需要使用,但依然要重写,空实现
    }
    @Override
    public void methodD() {
        //不需要使用,但依然要重写,空实现
    }
}

同理,接口请求类/使用类2(ClientB):

/**
 * 接口请求类/使用类 B
 * <p>
 * 只需要使用原接口中的methodB(),methodC()
 */
public class ClientB implements ISrc {
    @Override
    public void methodA() {
        //不需要使用,但依然要重写,空实现
    }
    @Override
    public void methodB() {
        //需要使用,重写后进行需要的操作
    }
    @Override
    public void methodC() {
        //需要使用,重写后进行需要的操作
    }
    @Override
    public void methodD() {
        //不需要使用,但依然要重写,空实现
    }
}

可看到,那些请求类不需要使用的方法,必须要重写(空实现)。随着后续请求类越多,这要重复的代码就会越来越多。而接口适配器就是消除请求类不要使用的方法的空实现,将空实现统一在一起(建立实现接口的抽象类:接口适配器类)。这样请求类就只需实现自己需要的方法即可。

3.3 代码实现

改造上述的例子,创建接口适配器类,即一个实现原接口的抽象类,每个方法都空实现:

/**
 * 适配器模式
 * 3.接口适配器
 * 创建接口适配器类,即一个实现原接口的抽象类,每个方法都空实现
 */
public abstract class InterfaceAdapter implements ISrc {
    @Override
    public void methodA() {

    }

    @Override
    public void methodB() {
        
    }
    
    @Override
    public void methodC() {
        
    }
    
    @Override
    public void methodD() {
        
    }
}

这样之后,请求类只需要继承自该适配器类,之后实现所需要使用的方法即可

修改后的 接口请求类/使用类1(ClientA):

/**
 * 接口请求类/使用类 A
 *
 * 只需要使用原接口中的methodA(),methodC()
 */
public class ClientA extends InterfaceAdapter {
    @Override
    public void methodA() {
        //需要使用,重写后进行需要的操作
    }

    @Override
    public void methodB() {
        //需要使用,重写后进行需要的操作
    }
}

同理修改后的 接口请求类/使用类2(ClientB):

public class ClientB extends InterfaceAdapter {
    @Override
    public void methodB() {
        //需要使用,重写后进行需要的操作
    }

    @Override
    public void methodC() {
        //需要使用,重写后进行需要的操作
    }
}

3.4 阶段小结

优点:减少了大量不必要的代码,之前有大量不需使用的方法,都需空实现。使用了接口适配器抽象类后,该抽象类的子类可选择性的覆盖父类方法来实现需求。

发布了83 篇原创文章 · 获赞 40 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_42391904/article/details/104167437