將程式分離成服務與對外窗口(介面),當外界要使用時,呼叫窗口即可,服務的一切不用知道。
起因是這樣子,假如現在有一個平台,且支援三個服務,最直覺的設計方式,便是設計出獨自連結服務的平台:
隨著支援平台增加到三個,維持上一個設計,那會演變成:
由此可見,該模式的數量公式為:平台數量 X 服務數量 = 模式總量
。
長遠來看,不是好的做法。
那不如把服務跟平台分離,服務歸服務,平台歸平台,換句話說,一個對內、一個對外,只要讓對外的能調用對內的,同時沒有錯誤產生,使用者自然覺得一切正常。
作法是:
Abstract Class
、Interface
。服務親代: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();
}
服務子代:LocalTrain
、PuyumaExpress
、TarokoExpress
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();
}
窗口子代:SingleTraveler
、FamilyTraveler
、ForeignTraveler
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();
}
}
服務親代: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; }
}
服務子代:LocalTrain
、PuyumaExpress
、TarokoExpress
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; }
}
窗口子代:SingleTraveler
、FamilyTraveler
、ForeignTraveler
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 模式。