Day 15: Structural patterns - Facade

目的

建立一个对外的窗口(介面),负责提供特定功能,而功能背後如何运作?与哪些物件有所关联?通通交给对外窗口来实践。

说明

让复杂的系统有一个对外的窗口,负责发号施令告知子系统该如何运作。

现实生活中,不少方便的事物对使用者来说都是简单,背後却有着复杂的步骤,例如:

  • 网购下单,经过下单成功、选定商品、装箱、运送、到指定取货点。
  • 股票下单,经过网路平台、下单、成交、告知缴纳金额、取得缴纳金额、完成交易。

换到程序上,可以分成三层:资料存取层、业务逻辑层、表示层,各层有各自的生态圈,如果放任彼此的联系方式,将导致过度耦合,未来的新增、修改将渐渐不容易。所以,适时地建立对外窗口,负责制定能提供的功能,各层之间依赖窗口沟通,进而简化耦合度

在网页开发上最有名的个案是 jQuery,提供一个简单的介面,背後却是非常复杂的运算。

jQuery运用Facade

实践的作法是:

  1. 建立对外窗口,了解需求後建立相关功能,负责与子系统沟通。
  2. 他人要使用该系统时,一律向对外窗口沟通。

UML 图

Facade Pattern UML Diagram

使用 Java 实作

子系统:DrinksVendingMachineMoneySystemShippingSystem

public class DrinksVendingMachine {
    private boolean isNormal;

    public DrinksVendingMachine() {
        isNormal = true;
    }

    public void welcome() {
        if (isNormal) {
            System.out.println("欢迎使用本机器");
            System.out.println("请投入硬币或纸钞");
        } else {
            System.out.println("机器故障,请联络厂商");
        }
    }

    public int getCoin() throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        return Integer.parseInt(br.readLine());
    }

    public void displayMoney(int money) {
        System.out.println("已投入 " + money + " 元");
        System.out.println("系统亮起可购买饮料");
    }

    public String chooseDrink() throws IOException {
        System.out.println("请选择饮料");
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        return br.readLine();
    }

    public void takeDrink(String drink) {
        System.out.println("客户已经取出" + drink);
    }

    public void getError() {
        isNormal = false;
    }
}

public class MoneySystem {
    private int currentMoney;

    public MoneySystem() {
        currentMoney = 0;
    }

    public void insertCoin(int value) {
        currentMoney += value;
    }

    public int showCurrentMoney() {
        return currentMoney;
    }
}

public class ShippingSystem {
    private String drinkName;

    public ShippingSystem() {
        drinkName = "";
    }

    public void setUpChooseDrink(String drink) {
        drinkName = drink;
    }

    public void openGate() {
        System.out.println("贩卖机底下出口已开启");
    }

    public void shipping() {
        System.out.println("运作" + drinkName + "的输送带");
        System.out.println(drinkName + "往下掉入出口");
        System.out.println("发出撞击声");
        System.out.println(drinkName + "可乐已抵达取出口");
    }

    public String getChosenDrink() {
        return drinkName;
    }
}

对外窗口:VendingMachineFacade

import java.io.IOException;

public class VendingMachineFacade {
    private DrinksVendingMachine dvm;
    private MoneySystem ms;
    private ShippingSystem ss;

    public VendingMachineFacade() {
        dvm = new DrinksVendingMachine();
        ms = new MoneySystem();
        ss = new ShippingSystem();
    }

    public void useIt() throws IOException {
        dvm.welcome();
        ms.insertCoin(dvm.getCoin());
        dvm.displayMoney(ms.showCurrentMoney());
        ss.setUpChooseDrink(dvm.chooseDrink());
        ss.shipping();
        dvm.takeDrink(ss.getChosenDrink());
    }
}

测试:DrinkFacadeSample

import java.io.IOException;

public class DrinkFacadeSample {
    public static void main(String[] args) throws IOException {
        VendingMachineFacade machine = new VendingMachineFacade();
        machine.useIt();
    }
}

使用 JavaScript 实作

设定环境,能够读取终端机的输入文字

const readline = require('readline');

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

const getUserInput = () => {
  return new Promise((resolve, reject) => {
    rl.question("", (input) => {
      resolve(input);
    });
  });
};

子系统:DrinksVendingMachineMoneySystemShippingSystem

class DrinksVendingMachine {
  constructor() {
    this.isNormal = true;
  }

  welcome() {
    if (this.isNormal) {
      console.log("欢迎使用本机器");
      console.log("请投入硬币或纸钞");
    } else {
      console.log("机器故障,请联络厂商");
    }
  }

  getCoin() {
    return getUserInput();
  }

  displayMoney(money) {
    console.log("已投入 " + money + " 元");
    console.log("系统亮起可购买饮料");
  }

  chooseDrink() {
    console.log("请选择饮料");
    return getUserInput();
  }

  takeDrink(drink) {
    console.log("客户已经取出" + drink);
  }

  getError() {
    this.isNormal = false;
  }
}

class MoneySystem {
  constructor() {
    this.currentMoney = 0;
  }

  insertCoin(value) {
    this.currentMoney += value;
  }

  showCurrentMoney() {
    return this.currentMoney;
  }
}

class ShippingSystem {
  constructor() {
    this.drinkName = "";
  }

  setUpChooseDrink(drink) {
    this.drinkName = drink;
  }

  openGate() {
    console.log("贩卖机底下出口已开启");
  }

  shipping() {
    console.log("运作" + this.drinkName + "的输送带");
    console.log(this.drinkName + "往下掉入出口");
    console.log("发出撞击声");
    console.log(this.drinkName + "已抵达取出口");
  }

  getChosenDrink() {
    return this.drinkName;
  }
}

对外窗口:VendingMachineFacade

class VendingMachineFacade {
  constructor() {
    this.dvm = new DrinksVendingMachine();
    this.ms = new MoneySystem();
    this.ss = new ShippingSystem();
  }

  async useIt() {
    this.dvm.welcome();
    this.ms.insertCoin(parseInt(await this.dvm.getCoin()));
    this.dvm.displayMoney(this.ms.showCurrentMoney());
    this.ss.setUpChooseDrink(await this.dvm.chooseDrink());
    this.ss.shipping();
    this.dvm.takeDrink(this.ss.getChosenDrink());
    rl.close();
  }
}

测试:drinkFacadeSample

const drinkFacadeSample = async () => {
  const machine = new VendingMachineFacade();
  await machine.useIt();
}

drinkFacadeSample();

总结

Facade 有趣在於,日常开发上经常使用,只是没有仔细思索过这样做的好处,因此在阅读相关文章时,除了脑中有经验可以马上连结之外,还能重新看待当初开发的过程,细细品味当时有没有尽力做好?是否有些对外窗口没有好好处理?再者,阅读自己的专案之外,阅读 jQuery 的原始档,慢慢能从中看出一些设计上的巧思,不再是当年那个刚踏入程序开发的自己了,同时对自己的成长感到开心。

明天将介绍 Structural patterns 的第六个模式:Flyweight 模式。


<<:  Day15-1.Two Sum

>>:  [Day15] 传值或传址(下)

[DAY 23]纠团通知功能(3/3)

纠团的功能我把它切成两个部分 使用者输入讯息 背景执行 今天介绍背景执行的部分 背景执行 这个部分主...

课堂笔记 - 深度学习 Deep Learning (2)

Machine Learning 上篇文章有简单提及Machine Learning的定义: Ma...

D6. 学习基础C、C++语言

D6: for回圈 最基本的for回圈样式是: for (变数初始值; 判断式; 递增式){ 陈述句...

终幕也是新的开始:请遵守软件版本周期

杯里的水并不多,再加上中途受其他外力改变倾倒的方向,所以只有键盘边缘沾上几滴水珠。 「⋯⋯学姐,刚刚...

Day24:24 - 结帐服务(8) - 前端 - 显示总订单资料、订单详情

Nnọọ,我是Charlie! 在Day23当中我们完成了订单资料的後端API,而今天我们将完成订单...