我所知道设计模式之适配器模式

28640

前言介绍


接下里介绍的是Java 的设计模式之一:适配器模式

如果你是第一次出国到美国旅行, 你会发现美国电源插头和插座标准与中国不同。这时你需要购买美国的电源插头与插座。

要是第二次出国到德国,到了德国又发现美国插头和德国插座不匹配

image.png

这时我们需要购买同时提供美国标准插座和欧洲标准插头的电源适配器可以解决这个难题

一、什么是适配器模式

适配器模式(Adapter Pattern):将某个类的接口转换成客户端期望的另一个接口表示

主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)

适配器模式属于结构型模式

主要分为三类:类适配器模式、对象适配器模式、接口适配器模式

二、工作原理


适配器模式:将一个类的接口转换成另一种接口.让原本接口不兼容的类可以兼容

从用户的角度看不到被适配者,是解耦的

用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法

用户收到反馈结果,感觉只是和目标接口交互,如图

image.png

三、类适配器模式

有一个Adapter通过继承src类,实现dst类接口,完成src->dst的适配。

应用实例说明

以生活中充电器的例子来讲解适配器:

  • 充电器本身相当于 Adapter
  • 220V 交流电相当于 src (即被适配者)
  • 我们的目 dst(即目标)是 5V 直流电压

image.png

根据类适配器的思路,我们的类图是这样的情况

image.png

按照我们的思路,先创建一个220v的电源类输出电压

//被适配的类
class Voltage220V {
    //输出 220V 的电压
    public int output220V() {
        int src = 220;
        System.out.println("电压=" + src + "伏");
        return src;
    }
}

接下里我们提供手机使用的5v电压,进行输出

//适配接口
interface IVoltage5V {
    public int output5V();
}

这样我们就需要一个适配器类将220v降压转为5v的电压

//适配器类
class VoltageAdapter extends Voltage220V implements IVoltage5V {

    @Override
    public int output5V() {
        //获取到 220V 电压
        int srcV = output220V();
        int dstV = srcV / 44 ; //降压处理转成 5v
        return dstV;
    }
}

接下里我们就可以让手机通过适配器,进行充电啦

class Phone {
    //充电
    public void charging(IVoltage5V iVoltage5V) {
        if (iVoltage5V.output5V() == 5) {
            System.out.println("电压为 5V, 可以充电~~");
        } else if (iVoltage5V.output5V() > 5) {
            System.out.println("电压大于 5V, 不能充电~~");
        }
    }
}

接下来让我们看看这样demo是怎么进行的呢?

public static void main(String[] args) {
    System.out.println(" === 类适配器模式 ====");
    Phone phone = new Phone();
    phone.charging(new VoltageAdapter());
}
运行结果如下:
=== 类适配器模式 ====
电压=220伏
电压为 5V, 可以充电~~

类适配器模式注意事项和细节

Java 是单继承机制,所以类适配器需要继承 src 类这一点算是一个缺点, 因为这要求 dst 必须是接口,有一定局限性;

src 类的方法在 Adapter 中 都会暴露出来,也增加了使用的成本。

但是其继承了 src 类,所以它可以根据需求重写 src 类的方法,使得 Adapter 的灵活性增强了。

四、对象适配器

在之前讲解的类适配器的时候,发现需要继承 src 类,添加了耦合度

而对象适配器和类适配器模式相同,只是将 Adapter 类作进行修改

我们将不是继承 src 类,而是持有 src 类的实例,以解决兼容性的问题。

即:持有 src 类,实现 dst 类接口,完成 src->dst 的适配

根据“合成复用原则”,在系统中尽量使用关联关系(聚合)来替代继承关系

根据对象适配器的思路,我们的类图是这样的情况

image.png

根据我们的思路,原先输出5v的接口不动,还是原来的样子

//适配接口
interface IVoltage5V {
    public int output5V();
}

并且原先一个220v的电源类输出电压也是无需改变的样子

//被适配的类
class Voltage220V {
    //输出 220V 的电压
    public int output220V() {
        int src = 220;
        System.out.println("电压=" + src + "伏");
        return src;
    }
}

接下来我们修改原先的适配器类,添加构造器传入220v输出类

//适配器类
class VoltageAdapter implements IVoltage5V {

    private Voltage220V voltage220V;

    public VoltageAdapter(Voltage220V voltage220V) {
        this.voltage220V = voltage220V;
    }

    @Override
    public int output5V() {
        int dstV = 0;
        if(null != voltage220V){
            //获取到 220V 电压
            int srcV = voltage220V.output220V();
            dstV = srcV / 44 ; //降压处理转成 5v
            System.out.println("适配完成,输出的电压为:" +dstV);
        }
        return dstV;
    }
}

接下里我们就可以让手机通过适配器,进行充电啦

class Phone {
    //充电
    public void charging(IVoltage5V iVoltage5V) {
        if (iVoltage5V.output5V() == 5) {
            System.out.println("电压为 5V, 可以充电~~");
        } else if (iVoltage5V.output5V() > 5) {
            System.out.println("电压大于 5V, 不能充电~~");
        }
    }
}

接下来让我们看看这样demo是怎么进行的呢?

public static void main(String[] args) {
    System.out.println(" === 类适配器模式 ====");
    Phone phone = new Phone();
    phone.charging(new VoltageAdapter(new Voltage220V()));
}
运行结果如下:
=== 类适配器模式 ====
电压=220伏
适配完成,输出的电压为:5
电压为 5V, 可以充电~~

对象适配器模式注意事项和细节

对象适配器和类适配器其实算是同一种思想,只不过实现方式不同

根据合成复用原则,使用组合替代继承, 所以它解决了类适配器必须继承 src 的局限性问题,也不再要求 dst必须是接口。

使用成本更低,更灵活

五、接口适配器模式

一些书籍称为:缺省适配器模式(有选择性)

核心思路:当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求

适用于一个接口不想使用其所有的方法的情况

应用实例说明

在Android 中的属性动画 ValueAnimator 类可以通过addListener(AnimatorListener listener)方法添加监听器

一般写的写法如下:

ValueAnimator valueAnimator = ValueAnimator.ofInt(0,100);
    
valueAnimator.addListener(new Animator.AnimatorListenerO{
    //默认实现(空方法)
    @Overmride
    public void onAnimationStant(Animator animation){}
    //默认实现(空方法)
    @Ovemide
    public void onAnimationEnd(Animator animation){}
    //默认实现(空方法)
    @Overide
    public void onAnimationCancel(Animator animation){}
    //默认实现(空方法)
    @Ovemide
    public woid onAnimationRepeat(Animator animation){}
});

有时候我们不想实现Animator.AnimatorListener 接口的全部方法,我们只想监听onAnimationStart,我们会如下写

ValueAnimator valueAnimator = ValueAnimator.ofInt(0,100);
    
valueAnimator.addListener(new Animator.AnimatorListenerO{
    //默认实现(空方法)
    @Overmride
    public void onAnimationStant(Animator animation){}
});

诶,为什么我们可以这样,原因在于有一个抽象类默认实现了空方法

首先是我们的接口:AnimatorListener

public static interface AnimatorListener {
    void onAnimationStart(Animator animation);
    void onAnimationEnd(Animator animation);
    void onAnimationCancel(Animator animation);
    void onAnimationRepeat(Animator animation);
}

接下来使我们的抽象类:AnimatorListenerAdapter

public abstract class AnimatorListenerAdapter implements Animator.AnimatorListener,
Animator.AnimatorPauseListener {

    //默认实现(空方法)
    @Overmride
    public void onAnimationStant(Animator animation){}
    //默认实现(空方法)
    @Ovemide
    public void onAnimationEnd(Animator animation){}
    //默认实现(空方法)
    @Overide
    public void onAnimationCancel(Animator animation){}
    //默认实现(空方法)
    @Ovemide
    public woid onAnimationRepeat(Animator animation){}
}

也就是说做了一个抽象类,实现了两个接口,并且以默认实现空方法

这就是为什么我们可以只写start方法也可以,重写自己关心的方法

那么我们的类图,其实就是这样的,可如下图所示

image.png

接下来我们使用demo 体会体会,首先先创一个接口

interface Interface4 {
    public void m1();
    public void m2();
    public void m3();
    public void m4();
}

同时我们根据思路创建Adapter抽象类继承这个接口,默认实现空方法

//在 AbsAdapter 我们将 Interface4 的方法进行默认实现
abstract class AbsAdapter implements Interface4 {

    //默认实现
    @Override
    public void m1() {}

    @Override
    public void m2() {}

    @Override
    public void m3() {}

    @Override
    public void m4() {}
}

当我们只需要实现m1的时候,可以看看demo是怎么做的?

public static void main(String[] args) {

    AbsAdapter absadapter = new AbsAdapter() {
        @Override
        public void m1() {
            System.out.println("使用m1的方法");
        }
    };
    absadapter.m1();
}
运行结果如下:
使用m1的方法

这里我们就采用了匿名内部类的方式去实现m1即可,无需全部实现

匿名内部类的定义

先看匿名内部类的定义的语法格式:

new 实现接口(){//匿名内部类类体部分}
new 父类构造器(实参列表){//匿名内部类类体部分}

这两种方式的定义分别对应两种方式,一种是接口,另一种是抽象类。

对于实现接口,由于接口是没有构造函数的,注意这里一定是空参数。

第二种是调用父类的构造器,注意此处可以是空参数,也可以传入参数

参考资料


尚硅谷:设计模式(韩顺平老师):适配器模式

Refactoring.Guru:《深入设计模式》

阅读 1.3k

心有多大,舞台就有多大

113 声望
25 粉丝
0 条评论

心有多大,舞台就有多大

113 声望
25 粉丝
文章目录
宣传栏