Day 11: Structural patterns - Adapter

目的

如何在不修改物件的情况上,使用中间层(Adapter)後,能转换跟其他物件联系,同时原有功能不受影响。

说明

这模式在现实最典型的应用是充电器:使用 110v 的电子设备,在国外 220v 的环境下,需要使用「变压器」让「充电」这个目的,仍能实践。

应用在软件开发上,可能有这些应用:

  • 系统要连线到不同种类的资料库,建立一个中间层,负责与新资料库的沟通。
  • 老旧系统只接受 XML,新系统只提供 JSON,需要中间层负责转换。

简而言之,就是建立一个「转换器」,让系统维持运作。

在实作前,要注意的是判断环境中,目标物件本身是单一物件还是继承虚拟层的物件?而作法则是:

  1. 注意目标物件本身是不是有继承虚拟层?还是单一物件?
    1. 如果有继承虚拟层,则建立一个「继承虚拟层」的转接器(Adapter),能提供与目标物件一样的方法。
    2. 如果是单一物件,则建立一个「继承目标物件」的转接器(Adapter),能提供与目标物件一样的方法。
  2. 转接器(Adapter)在个方法的实作上,会呼叫其他物件(Adaptee)的方法,达到使用其他物件的初衷。
  3. 当环境需要使用 Adaptee 时,使用 Adapter 即可。

以下 UML 图与范例程序码,采用「目标物件本身继承虚拟层」。

UML 图

Adapter Pattern UML Diagram

使用 Java 实作

亲代、虚拟层:Car

public abstract class Car {
    protected String name;

    protected Car(String name) {
        this.name = name;
    }

    public void turnOn() {
        System.out.println("发动车子,名称是:" + name);
    }

    public void turnOff() {
        System.out.println("车子熄火");
    }

    public abstract void turnRight();

    public abstract void turnLeft();

    public void speedUp() {
        System.out.println("脚踩油门");
    }

    public void brake() {
        System.out.println("脚踩煞车");
    }
}

子代:LeftHandCarRightHandCar(Adaptee)

public class LeftHandCar extends Car {
    public LeftHandCar(String name) {
        super(name);
    }

    @Override
    public void turnRight() {
        System.out.println("打右转灯");
        System.out.println("车辆靠右");
        System.out.println("确认没有行人");
        System.out.println("确认没有机车");
        System.out.println("车辆右转");
    }

    @Override
    public void turnLeft() {
        System.out.println("打左转灯");
        System.out.println("车辆向左靠近分隔岛");
        System.out.println("车辆向前靠近左转区块");
        System.out.println("等待左转灯亮起");
        System.out.println("左转灯亮起");
        System.out.println("车辆左转");
    }
}

public class RightHandCar extends Car {
    public RightHandCar(String name) {
        super(name);
    }

    @Override
    public void turnRight() {
        System.out.println("打右转灯");
        System.out.println("车辆向右靠近分隔岛");
        System.out.println("车辆向前靠近右转区块");
        System.out.println("等待右转灯亮起");
        System.out.println("右转灯亮起");
        System.out.println("车辆右转");
    }

    @Override
    public void turnLeft() {
        System.out.println("打左转灯");
        System.out.println("车辆靠左");
        System.out.println("确认没有行人");
        System.out.println("确认没有机车");
        System.out.println("车辆左转");
    }
}

右驾车适应在左驾环境:RightHandInLeftHandTraffic(Adapter)

public class RightHandInLeftHandTraffic extends RightHandCar {
    public RightHandInLeftHandTraffic(String name) {
        super(name);
    }

    @Override
    public void turnRight() {
        System.out.println("打右转灯");
        System.out.println("车辆靠右");
        System.out.println("确认没有行人");
        System.out.println("确认没有机车");
        System.out.println("车辆右转");
    }

    @Override
    public void turnLeft() {
        System.out.println("打左转灯");
        System.out.println("车辆向左靠近分隔岛");
        System.out.println("车辆向前靠近左转区块");
        System.out.println("等待左转灯亮起");
        System.out.println("左转灯亮起");
        System.out.println("车辆左转");
    }
}

在左驾的环境上路:LeftHandTraffic

public class LeftHandTraffic {
    public static void main(String[] args) {
        System.out.println("---国产车上路罗---");
        Car domesticCar = new LeftHandCar("Altis");
        domesticCar.turnOn();
        domesticCar.speedUp();
        domesticCar.brake();
        domesticCar.turnRight();
        domesticCar.speedUp();
        domesticCar.brake();
        domesticCar.turnLeft();
        domesticCar.turnOff();

        System.out.println("\n---完美,下车换日本进口车---");
        Car madeInJapanCar = new RightHandInLeftHandTraffic("CT 200h");
        madeInJapanCar.turnOn();
        madeInJapanCar.speedUp();
        madeInJapanCar.brake();
        madeInJapanCar.turnRight();
        madeInJapanCar.speedUp();
        madeInJapanCar.brake();
        madeInJapanCar.turnLeft();
        madeInJapanCar.turnOff();
        System.out.println("\n---测试完成,左驾右驾都很棒---");
    }
}

使用 JavaScript 实作

亲代、虚拟层:Car

/** @abstract */
class Car {
  constructor(name) {
    this.name = name;
  }

  turnOn() {
    console.log("发动车子,名称是:" + this.name);
  }

  turnOff() {
    console.log("车子熄火");
  }

  /** @abstract */
  turnRight() { return; }

  /** @abstract */
  turnLeft() { return; }

  speedUp() {
    console.log("脚踩油门");
  }

  brake() {
    console.log("脚踩煞车");
  }
}

子代:LeftHandCarRightHandCar(Adaptee)

class LeftHandCar extends Car {
  constructor(name) {
    super(name);
  }

  /** @override */
  turnRight() {
    console.log("打右转灯");
    console.log("车辆靠右");
    console.log("确认没有行人");
    console.log("确认没有机车");
    console.log("车辆右转");
  }

  /** @override */
  turnLeft() {
    console.log("打左转灯");
    console.log("车辆向左靠近分隔岛");
    console.log("车辆向前靠近左转区块");
    console.log("等待左转灯亮起");
    console.log("左转灯亮起");
    console.log("车辆左转");
  }
}

class RightHandCar extends Car {
  constructor(name) {
    super(name);
  }

  /** @override */
  turnRight() {
    console.log("打右转灯");
    console.log("车辆向右靠近分隔岛");
    console.log("车辆向前靠近右转区块");
    console.log("等待右转灯亮起");
    console.log("右转灯亮起");
    console.log("车辆右转");
  }

  /** @override */
  turnLeft() {
    console.log("打左转灯");
    console.log("车辆靠左");
    console.log("确认没有行人");
    console.log("确认没有机车");
    console.log("车辆左转");
  }
}

右驾车适应在左驾环境:RightHandInLeftHandTraffic(Adapter)

class RightHandInLeftHandTraffic extends RightHandCar {
  constructor(name) {
    super(name);
  }

  /** @override */
  turnRight() {
    console.log("打右转灯");
    console.log("车辆靠右");
    console.log("确认没有行人");
    console.log("确认没有机车");
    console.log("车辆右转");
  }

  /** @override */
  turnLeft() {
    console.log("打左转灯");
    console.log("车辆向左靠近分隔岛");
    console.log("车辆向前靠近左转区块");
    console.log("等待左转灯亮起");
    console.log("左转灯亮起");
    console.log("车辆左转");
  }
}

在左驾的环境上路:LeftHandTraffic

const leftHandTraffic = () => {
  console.log("---国产车上路罗---");
  const domesticCar = new LeftHandCar("Altis");
  domesticCar.turnOn();
  domesticCar.speedUp();
  domesticCar.brake();
  domesticCar.turnRight();
  domesticCar.speedUp();
  domesticCar.brake();
  domesticCar.turnLeft();
  domesticCar.turnOff();

  console.log("\n---完美,下车换日本进口车---");
  const madeInJapanCar = new RightHandInLeftHandTraffic("CT 200h");
  madeInJapanCar.turnOn();
  madeInJapanCar.speedUp();
  madeInJapanCar.brake();
  madeInJapanCar.turnRight();
  madeInJapanCar.speedUp();
  madeInJapanCar.brake();
  madeInJapanCar.turnLeft();
  madeInJapanCar.turnOff();
  console.log("\n---测试完成,左驾右驾都很棒---");
}

leftHandTraffic();

总结

Adapter 是非常容易理解的模式,理解是「专接器」後几乎懂含义。

倒是有几点要思考:

  • 如果一开始架构设计好使用多种不同类型的服务,那使用 Adapter 模式的机率就不高。换句话说,Adapter 模式较常运用在突发状况,在没办法重构的情况下,暂时处理机制。
  • 如果要作为第三方,专责支援其他环境的话,那在设计上采用 Adaptee 并建立许多 Adapter 是合理的做法。

除了今天介绍的应用,C++ 允许多重继承,所以 Adapter 有另一种写法,但想想这不是 JavaJavaScript 能实作的,便不深究了。

明天将介绍 Structural patterns 的第二个模式:Bridge 模式。


<<:  DAY11 - 前端网页怎麽处理档案?

>>:  学习Python纪录Day11 - 开启文字档案与写入资料

Day-19 Excel列印时常见问题

今日练习档 ԅ( ¯་། ¯ԅ) 大家好呀 ٩(ˊ〇ˋ*)و 列印我相信也是很多人会使用的一个功能,...

Day 12 MSW实战

MSW实战 今天我们来实战一个msw的使用,首先我们先随意建立一个component,我是建立一个U...

[DAY24]Istio-Gateway

K8s除了自带的Ingress Gateway外,还可以透过Istio Ingress Gatewa...

04. Unit Test x Cart Class

我想大部分的人学测试不是想用在写 leetcode 吧,因此我们来模拟一下购物车。 我们来写一个有点...

Day06. Blue Prism拯救贫穷大作战第一弹-汇入MS Excel VBO

Blue Prism拯救贫穷大作战第一弹-汇入MS Excel VBO 近期与之前工作的同事於lin...