在前文中,我們看了依賴注入怎幫助解耦合與提高擴展性。在文末,我們有講到,當你有很多長得很像的類別,大家做的事都差不多,但是彼此之間都差了一點點,這時候該怎麼辦?譬如我們拿一樣的殺手範例來看:
我們今天要做一個冷血殺手類別,這個殺手手上有一把手槍作為武器,每當要殺人時,他就會開槍,發出砰然巨響。
一個搞笑殺手類別,這個殺手手上不管有什麼,叫他殺人時他只會講笑話。
一個謹慎殺手類別,這個殺手做的事跟冷血殺手類似,但是做案完會記得清理現場。
一個見習殺手類別,這個見習殺手只會看,然後事成後幫忙清理現場。
不要急,我知道,你會說co一co很快,好好好,我當然知道你很快,但我們今天想要做的事,是要建立一個好的軟體工程範例,也就是要符合物件導向精神中的Don't Repeat Yourself的coding精神。要怎樣,才能將重複又類似的程式碼撿到做少呢?我們來看看 -- 『模板』設計模式吧。
首先我們還是要來重組需求,我自己的經驗,看到這種問題,一般會採如下的分析方法:
那我們就開始嚕,先把動作定義出來。這裡,我們不更動上文給的interface,而是創立一個抽象類別(Abstract Class),並且將這些動作定義在這裡:
// TemplateKiller.java
public abstract class TemplateKiller implements Killer {
Weapon weapon;
protected TemplateKiller(Weapon weapon){
this.weapon = weapon;
}
protected void useWeapon(){
this.weapon.act();
}
protected void tellJoke(){
System.out.println("I will tell you a joke");
}
protected void clear(){
System.out.println("I am cleaning. I am cleaning.");
}
protected void watch(){
System.out.println("Shhh!!!! Watching...");
}
}
這裡為止,我們做的是模板本身,我們定義了所有殺手可能的動作,但是卻不規定當我們命令他殺人時,他會做的事情。因為當收到指令時會做上面四個動作的那些事,是各種殺手自己要定義的,就像這樣:
//CoolKiller只使用武器
public class CoolKiller extends TemplateKiller {
public CoolKiller(Weapon weapon) {
super(weapon);
}
public void kill() {
useWeapon();
}
}
//FunnyKiller只說笑話
public class FunnyKiller extends TemplateKiller {
protected FunnyKiller(Weapon weapon) {
super(weapon);
}
public void kill() {
tellJoke();
}
}
//CalmKiller做案完會清理現場
public class CalmKiller extends TemplateKiller {
protected CalmKiller(Weapon weapon) {
super(weapon);
}
public void kill() {
useWeapon();
clear();
}
}
//InternKiller只跟著看和清理現場
public class InternKiller extends TemplateKiller {
protected InternKiller(Weapon weapon) {
super(weapon);
}
public void kill() {
watch();
clear();
}
}
這樣一來,不管你要創造任何一種Killer,就只要給他一把武器,再叫他執行kill(),他就會依照他真正時做的kill內容去執行任務了,是不是乾淨又優雅?
喔對了,記得我們先前為CoolKiller寫的test嗎?我們現在試試看完全不動Test內容,直接跑看看:
注意:我這是一行Test都沒動喔!
果真過了!(呼~好險)
為什麼會過?你看,我們只修改了CoolKiller的實踐方式,讓他去call模板的『使用武器』功能,跟他原本做的事其實一樣,當然會過啊!如果沒過,原因只可能有二:
自動測試的功用在這裡就顯現出來了。有了自動測試,我們在做這種路過幫修式的重構時,就比較有信心,才不會怕一個不小心又把正確的東西改做了而變得投鼠忌器。好的設計模式跟足夠信心的測試互相搭配,長期下來不管是程式品質或是擴展速度都是有幫助的。對了。你抓到重點了:
自動測試最大的幫助,不是在保證功能正確,而是『確保在增加新功能時不會破壞舊功能』
而,正如前一篇所說,設計模式五花八門各式各樣,各有其巧妙,也各有其適合的情境,還是得要多學習、多練習才能順手。