【设计模式】适配器模式

适配器模式的意图

  • 将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能在一起工作的那些类可以一起工作(就是把接口不兼容的问题抹平,但不能解决功能不一致的问题)

两种方式

  • 类适配器采用“多继承”的实现方式,带来了不良的高耦合,所以一般不使用
  • 对象适配器采用“对象组合”的方式,更符合松耦合的精神

生活用例(防止看不懂意图)(这里我参考了精读《设计模式 - Adapter 适配器模式》这个博主的文章

1、接口转换器

插座的种类很多,我们用过很多适配器,将不同的插头进行·转换,可以在不替换插座的情况下正常使用。
这就是我们在生活中使用到的适配器模式,厂商并没有生产一个新的插座,我们也不会因为接口不适配而换一个手机,一切都只需要一个接口转换器就可以。

2、数据库ORM

ORM屏蔽了SQL这一层,好处就是不需要理解不同的SQL语法之间的区别,对于通用功能,可以根据不同的平台(比如MySQL),来进行SQL的转换
所以对于ORM来说,屏蔽不同平台的差异,一样是适配器模式做到的。

3、API Deprecated

当一个广泛使用的库进行了含有 break change 的升级时,往往要留给开发者足够的时间去升级,而不能升级后就直接挂掉,因此被废弃的 API 要标记为 deprecated,而这种被废弃标记的 API 的实际实现,往往是使用新的 API 替代,这种场景正是使用了适配器模式,将新的 API 适配到旧的 API,实现 API Deprecated。(就是新的API替换旧的API,我们需要一个一个适配器来进行转换)

解决了什么问题

  • 解决了多个接口之间不兼容的问题(通过增加一个适配器类,来通过java中的继承和实现接口来实现,不同的语言实现不同但想法相同)

结构图

  1. Target(目标抽象类):目标抽象类定义客户所需接口

  2. Adapter(适配器类):适配器可以调用另一个接口,作为一个转换器,可以对Target和Adaptee进行适配。(来给两个类之间的不兼容进行缓冲)

  3. Adaptee(适配者类):定义一个以及存在的接口,这个接口需要适配来能让其他接口来访问。所以适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。

  • 原理:当客户端需要调用一个适配者类没有的方法(比如request())的时候,但是适配者类中有一个方法(specificRequest())需要被使用到,这时候我们可以使用一个适配器类,这个适配器类包装类适配者的实例,从而将适配者和客户端进行衔接起来,这样就可以在适配器类的方法(request())调用适配者类的specificRequest()方法。

类适配器和对象适配器

区别:类适配器模式中适配器和适配者是继承关系,对象适配器模式中适配器和适配者是关联关系,但是大部分情况使用的是对象适配器

应用实例

设计一个可以模拟各种动物行为的机器人,在机器人中定义了一系列的方法,如机器人叫喊的方法cry(),机器人移动的方法move()等,使得机器人能够像鸟一样叫,像狗一样跑,分别使用对象适配器以及类适配器实现。

适配者

public interface Robot {

    /**
     * 像鸟一样叫
     */
    public void birdCall();

    /**
     * 像狗一样跑
     */
    public void dogMove();

}

目标抽象类

public class Dog {

    public void wang(){

        System.out.println("狗在汪汪叫!");

    }

    public void dogMove(){

        System.out.println("狗在地上跑!");

    }
}
public class Bird {

    public void BirdCall(){
        System.out.println("鸟叫!");;
    }

}

创建适配器类

public class Adapter implements Robot{

    Dog dog;
    Bird bird;

    public Adapter(Dog dog, Bird bird){
        this.dog = dog;
        this.bird = bird;
    }

    @Override
    public void birdCall() {
        System.out.print("机器人在模仿");
        bird.BirdCall();
    }

    @Override
    public void dogMove() {
        System.out.print("机器人在模仿");
        dog.dogMove();
    }

}

实现类

public class Main {

    public static void main(String[] args) {

        Bird bird = new Bird();
        Dog dog = new Dog();
        // 传入狗和鸟的信息
        Robot robot = new Adapter(dog, bird);
        robot.birdCall();
        robot.dogMove();

    }

}

优缺点

优点

  • 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无需对源代码进行修改
  • 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。(总结就是更好的复用性)
  • 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。(总结就是更好的可扩展性)
  • 一个对象适配器可以把多个不同的适配者适配到同一个目标。
  • 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据“里氏代换原则”,适配者的子类也可通过该适配器进行适配。

缺点

  • 对象适配器要在适配器中置换适配者类的某些方法比较麻烦。如果一定要置换掉适配者类的一个或多个方法,可以先做一个适配者类的子类,将适配者类的方法置换,然后再把适配者类的子类当作真正的适配者进行适配,这样实现较为复杂

使用时需要注意什么

  • 在适配器模式定义中所提及的接口是指广义的接口,它可以表示一个方法或者方法的集合。

值得看一下的链接

适配器模式详解与使用示例
使用C++实现适配器类要注意什么问题

适用场景

  • 系统需要使用现有的类,但是此类的接口并不符合系统需求
  • 想要建立一个有以重复使用的类,但是这个类和那些接口不一定兼容的类,就需要一个适配器类
  • 对象适配器)在设计里,想要使用一些已经存在的子类,但是不可能对每个子类进行适配,对象适配器就可以适配它的父类接口。
posted @ 2022-11-03 16:54  雨季悠理  阅读(174)  评论(0编辑  收藏  举报