Day 12: Structural patterns - Bridge

目的

将程序分离成服务与对外窗口(介面),当外界要使用时,呼叫窗口即可,服务的一切不用知道。

说明

起因是这样子,假如现在有一个平台,且支援三个服务,最直觉的设计方式,便是设计出独自连结服务的平台:
Simple Bridge Sample

随着支援平台增加到三个,维持上一个设计,那会演变成:
Complex Bridge Sample

由此可见,该模式的数量公式为:平台数量 X 服务数量 = 模式总量

长远来看,不是好的做法。

那不如把服务跟平台分离,服务归服务,平台归平台,换句话说,一个对内、一个对外,只要让对外的能调用对内的,同时没有错误产生,使用者自然觉得一切正常。

作法是:

  1. 建立一个服务的亲代,负责开规格,方便对外窗口的呼叫,可以是 Abstract ClassInterface
  2. 建立服务子代,实作亲代的规格细节。
  3. 建立窗口亲代,负责开规格,方便使用者呼叫。
  4. 建立窗口子代,实作亲代的规格细节。

UML 图

Bridge Pattern UML Diagram

使用 Java 实作

服务亲代:Train

public interface Train {
    public abstract String checkName();

    public abstract boolean isOnTime();

    public abstract void setTime(int min);

    public abstract void goToDestination();

    public abstract boolean getToiletStatus();

    public abstract void setToiletStatus(boolean using);

    public abstract void moveOn();

    public abstract void stop();

    public abstract void getFoodByTrolleyService();

    public abstract void timeNeedToArrive();

    public abstract void getEmergencies();
}

服务子代:LocalTrainPuyumaExpressTarokoExpress

public class LocalTrain implements Train {
    protected String name;
    protected boolean onTime;
    protected int delayMins;
    protected boolean toiletInUsing;
    protected boolean hasTrolleyService;

    public LocalTrain() {
        this.name = "区间车";
        this.onTime = true;
        this.delayMins = 0;
        this.toiletInUsing = false;
        this.hasTrolleyService = false;
    }

    @Override
    public String checkName() {
        return name;
    }

    @Override
    public boolean isOnTime() {
        return onTime;
    }

    @Override
    public void setTime(int min) {
        delayMins += min;
        onTime = (delayMins < 0);
    }

    @Override
    public void goToDestination() {
        System.out.println("抵达目的地");
    }

    @Override
    public boolean getToiletStatus() {
        return toiletInUsing;
    }

    @Override
    public void setToiletStatus(boolean using) {
        toiletInUsing = using;
    }

    @Override
    public void moveOn() {
        System.out.println("列车向前");
    }

    @Override
    public void stop() {
        System.out.println("列车停驶");
    }

    @Override
    public void getFoodByTrolleyService() {
        System.out.println("难过,没有东西可买");

    }

    @Override
    public void timeNeedToArrive() {
        System.out.println("还需要 " + delayMins + "分钟才能抵达");
    }

    @Override
    public void getEmergencies() {
        // 1 - 5
        int option = (int) (Math.random() * (6 - 1 + 1)) + 1;

        switch (option) {
            case 1:
                System.out.println("撞倒擅闯平交道的车");
                setTime(-50);
                stop();
                moveOn();
                timeNeedToArrive();
                goToDestination();
                break;
            case 2:
                System.out.println("肚子有点饿,想买东西");
                getFoodByTrolleyService();
                setTime(11);
                timeNeedToArrive();
                goToDestination();
                break;
            case 3:
                System.out.println("想上厕所但有人");
                setToiletStatus(true);
                getToiletStatus();
                setTime(-1);
                timeNeedToArrive();
                goToDestination();
                break;
            case 4:
                System.out.println("想上厕所");
                setToiletStatus(false);
                getToiletStatus();
                setTime(-7);
                timeNeedToArrive();
                goToDestination();
                break;
            case 5:
                System.out.println("一路顺畅");
                timeNeedToArrive();
                goToDestination();
                break;
            case 6:
                System.out.println("座位没了,只好站着");
                setTime(-12);
                timeNeedToArrive();
                goToDestination();
                break;
        }
    }
}

public class PuyumaExpress implements Train {
    protected String name;
    protected boolean onTime;
    protected int delayMins;
    protected boolean toiletInUsing;
    protected boolean hasTrolleyService;

    public PuyumaExpress() {
        this.name = "普悠玛";
        this.onTime = true;
        this.delayMins = 0;
        this.toiletInUsing = false;
        this.hasTrolleyService = true;
    }

    @Override
    public String checkName() {
        return name;
    }

    @Override
    public boolean isOnTime() {
        return onTime;
    }

    @Override
    public void setTime(int min) {
        delayMins += min;
        onTime = (delayMins < 0);
    }

    @Override
    public void goToDestination() {
        System.out.println("很快抵达目的地");
    }

    @Override
    public boolean getToiletStatus() {
        return toiletInUsing;
    }

    @Override
    public void setToiletStatus(boolean using) {
        toiletInUsing = using;
    }

    @Override
    public void moveOn() {
        System.out.println("列车向前");
    }

    @Override
    public void stop() {
        System.out.println("列车停驶");
    }

    @Override
    public void getFoodByTrolleyService() {
        if (hasTrolleyService) {
            System.out.println("服务员用推车贩售食物");
        } else {
            System.out.println("难过,没有东西可买");
        }

    }

    @Override
    public void timeNeedToArrive() {
        System.out.println("还需要 " + delayMins + "分钟才能抵达");
    }

    @Override
    public void getEmergencies() {
        // 1 - 4
        int option = (int) (Math.random() * (4 - 1 + 1)) + 1;

        switch (option) {
            case 1:
                System.out.println("肚子有点饿,想买东西");
                getFoodByTrolleyService();
                setTime(-3);
                timeNeedToArrive();
                goToDestination();
                break;
            case 2:
                System.out.println("上下车花费太多时间");
                setTime(-15);
                moveOn();
                timeNeedToArrive();
                goToDestination();
                break;
            case 3:
                System.out.println("想上厕所");
                setToiletStatus(false);
                setTime(12);
                getToiletStatus();
                timeNeedToArrive();
                goToDestination();
                break;
            case 4:
                System.out.println("一路顺畅");
                timeNeedToArrive();
                goToDestination();
                break;
        }
    }
}

public class TarokoExpress implements Train {
    protected String name;
    protected boolean onTime;
    protected int delayMins;
    protected boolean toiletInUsing;
    protected boolean hasTrolleyService;

    public TarokoExpress() {
        this.name = "太鲁阁";
        this.onTime = true;
        this.delayMins = 0;
        this.toiletInUsing = false;
        this.hasTrolleyService = true;
    }

    @Override
    public String checkName() {
        return name;
    }

    @Override
    public boolean isOnTime() {
        return onTime;
    }

    @Override
    public void setTime(int min) {
        delayMins += min;
        onTime = (delayMins < 0);
    }

    @Override
    public void goToDestination() {
        System.out.println("开心地抵达目的地");
    }

    @Override
    public boolean getToiletStatus() {
        return toiletInUsing;
    }

    @Override
    public void setToiletStatus(boolean using) {
        toiletInUsing = using;
    }

    @Override
    public void moveOn() {
        System.out.println("列车向前");
    }

    @Override
    public void stop() {
        System.out.println("列车停驶");
    }

    @Override
    public void getFoodByTrolleyService() {
        if (hasTrolleyService) {
            System.out.println("服务员用推车贩售食物");
        } else {
            System.out.println("难过,没有东西可买");
        }

    }

    @Override
    public void timeNeedToArrive() {
        System.out.println("还需要 " + delayMins + "分钟才能抵达");
    }

    @Override
    public void getEmergencies() {
        // 1 - 4
        int option = (int) (Math.random() * (4 - 1 + 1)) + 1;

        switch (option) {
            case 1:
                System.out.println("因为平交道上障碍物所以停车");
                setTime(-50);
                stop();
                moveOn();
                timeNeedToArrive();
                goToDestination();
                break;
            case 2:
                System.out.println("肚子有点饿,想买东西");
                getFoodByTrolleyService();
                setTime(220);
                timeNeedToArrive();
                goToDestination();
                break;
            case 3:
                System.out.println("想上厕所但有人");
                setToiletStatus(true);
                getToiletStatus();
                setTime(1);
                timeNeedToArrive();
                goToDestination();
                break;
            case 4:
                System.out.println("一路顺畅");
                timeNeedToArrive();
                goToDestination();
                break;
        }
    }
}

窗口亲代:Traveler

public interface Traveler {
    public abstract void checkTicket(Train train);

    public abstract void getJourney();
}

窗口子代:SingleTravelerFamilyTravelerForeignTraveler

public class SingleTraveler implements Traveler {
    private String name;
    private Train ticket;

    public SingleTraveler(String name) {
        this.name = name;
    }

    @Override
    public void checkTicket(Train train) {
        this.ticket = train;
        System.out.println("我是 " + name + ",确认车种为:" + ticket.checkName());
    }

    @Override
    public void getJourney() {
        System.out.println("本人 " + name + " 的旅途即将开始");
        ticket.getEmergencies();
    }
}

public class FamilyTraveler implements Traveler {
    private String name;
    private int children;
    private Train ticket;

    public FamilyTraveler(String name, int childrenCount) {
        this.name = name;
        this.children = childrenCount;
    }

    @Override
    public void checkTicket(Train train) {
        this.ticket = train;
        System.out.println("我是 " + name + ",确认车种为:" + ticket.checkName());
    }

    @Override
    public void getJourney() {
        System.out.println("与 " + children + " 个孩子的旅途即将开始");
        ticket.getEmergencies();
    }
}

public class ForeignTraveler implements Traveler {
    private String name;
    private String country;
    private Train ticket;

    public ForeignTraveler(String name, String country) {
        this.name = name;
        this.country = country;
    }

    @Override
    public void checkTicket(Train train) {
        this.ticket = train;
        System.out.println("My name is " + name + ",the train is:" + ticket.checkName());
    }

    @Override
    public void getJourney() {
        System.out.println("I miss my country: " + country + " , but the new journey is so adorable");
        ticket.getEmergencies();
    }
}

测试:JourneyBridgePatternSample

public class JourneyBridgePatternSample {
    public static void main(String[] args) {
        System.out.println("---一人的旅程开始---");
        System.out.println("这次的车种是:区间车");
        SingleTraveler singleTraveler = new SingleTraveler("维特");
        singleTraveler.checkTicket(new LocalTrain());
        singleTraveler.getJourney();

        System.out.println("\n---下一组是家庭的旅程---");
        System.out.println("车种为:普悠玛");
        FamilyTraveler familyTraveler = new FamilyTraveler("罗杰", 3);
        familyTraveler.checkTicket(new PuyumaExpress());
        familyTraveler.getJourney();

        System.out.println("\n---最後是外国人的旅程---");
        System.out.println("虽然想家,仍把握机会搭乘:太鲁阁");
        ForeignTraveler foreignTraveler = new ForeignTraveler("David", "US");
        foreignTraveler.checkTicket(new TarokoExpress());
        foreignTraveler.getJourney();
    }
}

使用 JavaScript 实作

服务亲代:Train

/** @interface */
class Train {
  constructor(name, hasTrolleyService) {
    this.name = name;
    this.onTime = true;
    this.delayMins = 0;
    this.toiletInUsing = false;
    this.hasTrolleyService = hasTrolleyService;
  }
  /** @abstract */
  checkName() { return; }
  /** @abstract */
  isOnTime() { return; }
  /** @abstract */
  setTime(min) { return; }
  /** @abstract */
  goToDestination() { return; }
  /** @abstract */
  getToiletStatus() { return; }
  /** @abstract */
  setToiletStatus(using) { return; }
  /** @abstract */
  moveOn() { return; }
  /** @abstract */
  stop() { return; }
  /** @abstract */
  getFoodByTrolleyService() { return; }
  /** @abstract */
  timeNeedToArrive() { return; }
  /** @abstract */
  getEmergencies() { return; }
}

服务子代:LocalTrainPuyumaExpressTarokoExpress

class LocalTrain extends Train {
  constructor() {
    super("区间车", false);
  }

  /** @override */
  checkName() {
    return this.name;
  }

  /** @override */
  isOnTime() {
    return this.onTime;
  }

  /** @override */
  setTime(min) {
    this.delayMins += min;
    this.onTime = (this.delayMins < 0);
  }

  /** @override */
  goToDestination() {
    console.log("抵达目的地");
  }

  /** @override */
  getToiletStatus() {
    return this.toiletInUsing;
  }

  /** @override */
  setToiletStatus(using) {
    this.toiletInUsing = using;
  }

  /** @override */
  moveOn() {
    console.log("列车向前");
  }

  /** @override */
  stop() {
    console.log("列车停驶");
  }

  /** @override */
  getFoodByTrolleyService() {
    console.log("难过,没有东西可买");

  }

  /** @override */
  timeNeedToArrive() {
    console.log("还需要 " + this.delayMins + "分钟才能抵达");
  }

  /** @override */
  getEmergencies() {
    // 1 - 5
    const option = Math.floor(Math.random() * (6 - 1 + 1)) + 1;

    switch (option) {
      case 1:
        console.log("撞倒擅闯平交道的车");
        this.setTime(-50);
        this.stop();
        this.moveOn();
        this.timeNeedToArrive();
        this.goToDestination();
        break;
      case 2:
        console.log("肚子有点饿,想买东西");
        this.getFoodByTrolleyService();
        this.setTime(11);
        this.timeNeedToArrive();
        this.goToDestination();
        break;
      case 3:
        console.log("想上厕所但有人");
        this.setToiletStatus(true);
        this.getToiletStatus();
        this.setTime(-1);
        this.timeNeedToArrive();
        this.goToDestination();
        break;
      case 4:
        console.log("想上厕所");
        this.setToiletStatus(false);
        this.getToiletStatus();
        this.setTime(-7);
        this.timeNeedToArrive();
        this.goToDestination();
        break;
      case 5:
        console.log("一路顺畅");
        this.timeNeedToArrive();
        this.goToDestination();
        break;
      case 6:
        console.log("座位没了,只好站着");
        this.setTime(-12);
        this.timeNeedToArrive();
        this.goToDestination();
        break;
    }
  }
}

class PuyumaExpress extends Train {
  constructor() {
    super("普悠玛", true);
  }

  /** @override */
  checkName() {
    return this.name;
  }

  /** @override */
  isOnTime() {
    return this.onTime;
  }

  /** @override */
  setTime(min) {
    this.delayMins += min;
    this.onTime = (this.delayMins < 0);
  }

  /** @override */
  goToDestination() {
    console.log("很快抵达目的地");
  }

  /** @override */
  getToiletStatus() {
    return this.toiletInUsing;
  }

  /** @override */
  setToiletStatus(using) {
    this.toiletInUsing = using;
  }

  /** @override */
  moveOn() {
    console.log("列车向前");
  }

  /** @override */
  stop() {
    console.log("列车停驶");
  }

  /** @override */
  getFoodByTrolleyService() {
    if (this.hasTrolleyService) {
      console.log("服务员用推车贩售食物,买了鸡腿便当");
    } else {
      console.log("难过,没有东西可买");
    }

  }

  /** @override */
  timeNeedToArrive() {
    console.log("还需要 " + this.delayMins + "分钟才能抵达");
  }

  /** @override */
  getEmergencies() {
    // 1 - 4
    const option = Math.floor(Math.random() * (4 - 1 + 1)) + 1;

    switch (option) {
      case 1:
        console.log("肚子有点饿,想买东西");
        this.getFoodByTrolleyService();
        this.setTime(-3);
        this.timeNeedToArrive();
        this.goToDestination();
        break;
      case 2:
        console.log("上下车花费太多时间");
        this.setTime(-15);
        this.moveOn();
        this.timeNeedToArrive();
        this.goToDestination();
        break;
      case 3:
        console.log("想上厕所");
        this.setToiletStatus(false);
        this.setTime(12);
        this.getToiletStatus();
        this.timeNeedToArrive();
        this.goToDestination();
        break;
      case 4:
        console.log("一路顺畅");
        this.timeNeedToArrive();
        this.goToDestination();
        break;
    }
  }
}

class TarokoExpress extends Train {
  constructor() {
    super("太鲁阁", true);
  }

  /** @override */
  checkName() {
    return this.name;
  }

  /** @override */
  isOnTime() {
    return this.onTime;
  }

  /** @override */
  setTime(min) {
    this.delayMins += min;
    this.onTime = (this.delayMins < 0);
  }

  /** @override */
  goToDestination() {
    console.log("开心地抵达目的地");
  }

  /** @override */
  getToiletStatus() {
    return this.toiletInUsing;
  }

  /** @override */
  setToiletStatus(using) {
    this.toiletInUsing = using;
  }

  /** @override */
  moveOn() {
    console.log("列车向前");
  }

  /** @override */
  stop() {
    console.log("列车停驶");
  }

  /** @override */
  getFoodByTrolleyService() {
    if (this.hasTrolleyService) {
      console.log("服务员用推车贩售食物,买了排骨便当");
    } else {
      console.log("难过,没有东西可买");
    }

  }

  /** @override */
  timeNeedToArrive() {
    console.log("还需要 " + this.delayMins + "分钟才能抵达");
  }

  /** @override */
  getEmergencies() {
    // 1 - 4
    const option = Math.floor(Math.random() * (4 - 1 + 1)) + 1;

    switch (option) {
      case 1:
        console.log("因为平交道上障碍物所以停车");
        this.setTime(-50);
        this.stop();
        this.moveOn();
        this.timeNeedToArrive();
        this.goToDestination();
        break;
      case 2:
        console.log("肚子有点饿,想买东西");
        this.getFoodByTrolleyService();
        this.setTime(20);
        this.timeNeedToArrive();
        this.goToDestination();
        break;
      case 3:
        console.log("想上厕所但有人");
        this.setToiletStatus(true);
        this.getToiletStatus();
        this.setTime(1);
        this.timeNeedToArrive();
        this.goToDestination();
        break;
      case 4:
        console.log("一路顺畅");
        this.timeNeedToArrive();
        this.goToDestination();
        break;
    }
  }
}

窗口亲代:Traveler

/** @interface */
class Traveler {
  constructor(name) {
    this.name = name;
    this.ticket = null;
  }
  /** @abstract */
  checkTicket(train) { return; }
  /** @abstract */
  getJourney() { return; }
}

窗口子代:SingleTravelerFamilyTravelerForeignTraveler

class SingleTraveler extends Traveler {
  constructor(name) {
    super(name);
  }

  /** @override */
  checkTicket(train) {
    this.ticket = train;
    console.log("我是 " + this.name + ",确认车种为:" + this.ticket.checkName());
  }

  /** @override */
  getJourney() {
    console.log("本人 " + this.name + " 的旅途即将开始");
    this.ticket.getEmergencies();
  }
}

class FamilyTraveler extends Traveler {
  constructor(name, childrenCount) {
    super(name);
    this.children = childrenCount;
  }

  /** @override */
  checkTicket(train) {
    this.ticket = train;
    console.log("我是 " + this.name + ",确认车种为:" + this.ticket.checkName());
  }

  /** @override */
  getJourney() {
    console.log("与 " + this.children + " 个孩子的旅途即将开始");
    this.ticket.getEmergencies();
  }
}

class ForeignTraveler extends Traveler {
  constructor(name, country) {
    super(name);
    this.country = country;
  }

  /** @override */
  checkTicket(train) {
    this.ticket = train;
    console.log("My name is " + this.name + ",the train is:" + this.ticket.checkName());
  }

  /** @override */
  getJourney() {
    console.log("I miss my country: " + this.country + " , but the new journey is so adorable");
    this.ticket.getEmergencies();
  }
}

测试:journeyBridgePatternSample

const journeyBridgePatternSample = () => {
  console.log("---一人的旅程开始---");
  console.log("这次的车种是:区间车");
  const singleTraveler = new SingleTraveler();
  singleTraveler.checkTicket(new LocalTrain());
  singleTraveler.getJourney();

  console.log("\n---下一组是家庭的旅程---");
  console.log("车种为:普悠玛");
  const familyTraveler = new FamilyTraveler("罗杰", 3);
  familyTraveler.checkTicket(new PuyumaExpress());
  familyTraveler.getJourney();

  console.log("\n---最後是外国人的旅程---");
  console.log("虽然想家,仍把握机会搭乘:太鲁阁");
  const foreignTraveler = new ForeignTraveler("David", "US");
  foreignTraveler.checkTicket(new TarokoExpress());
  foreignTraveler.getJourney();
};

journeyBridgePatternSample();

总结

Bridge 算是在开发上很常使用的模式,在於分离,让物件们可以专责在自己的功能上。过往的开发经验上,时常使用该模式,但省略使用虚拟层开规格这步骤。经由这次的学习,了解先写下规格後,方便之後开发服务时知道哪些功能要开发,而对外窗口在呼叫上也能省略再三确认的麻烦。

缺点也是十分明显:

  • 随着程序码的复杂度上升後,虚拟层要怎麽开?
  • 服务实作时,如何同时维持规格以及内部的商业逻辑?是否会过度复杂化?

这些仰赖经验来判断,讲多了还是亲自面对专案时,才能慢慢思考出合适的做法。

明天将介绍 Structural patterns 的第三个模式:Composite 模式。


<<:  Day12 NodeJS-Web Server I

>>:  Batch Processing (4) - Materialization of Intermediate State

未来狂想:天气气候监测领域

人的科技文明发展始终来自於人性 在科技进步的情况之下,我们已经习惯於使用科技的帮助来介入我们的生活,...

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

开启文字档案与写入资料 Python使用内建函式open()开启档案和close()关闭档案。 开启...

JS语法学习Day5

学习目标 if判断&switch case 、取得html元素 if判断 if(条件)-&g...

认识JavaScrip

JavaScript(通常缩写为JS)是可以内嵌於网页中,是一个成熟的动态程序语言,在网站里加入互动...

【Day 29】函式(下)

昨天我们讨论的函式,是没有返回数值的函式,只是单纯传入参数做运算後,直接输出。但我们更多时候会需要把...