建立一個對外的窗口(介面),負責提供特定功能,而功能背後如何運作?與哪些物件有所關聯?通通交給對外窗口來實踐。
讓複雜的系統有一個對外的窗口,負責發號施令告知子系統該如何運作。
現實生活中,不少方便的事物對使用者來說都是簡單,背後卻有著複雜的步驟,例如:
換到程式上,可以分成三層:資料存取層、業務邏輯層、表示層,各層有各自的生態圈,如果放任彼此的聯繫方式,將導致過度耦合,未來的新增、修改將漸漸不容易。所以,適時地建立對外窗口,負責制定能提供的功能,各層之間依賴窗口溝通,進而簡化耦合度
在網頁開發上最有名的個案是 jQuery
,提供一個簡單的介面,背後卻是非常複雜的運算。
實踐的作法是:
子系統:DrinksVendingMachine
、MoneySystem
、ShippingSystem
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();
}
}
設定環境,能夠讀取終端機的輸入文字
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);
});
});
};
子系統:DrinksVendingMachine
、MoneySystem
、ShippingSystem
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 模式。