iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 14
0
自我挑戰組

再戰軟體工程系列 第 13

『有點像又不會太一樣』 -- 慎選設計模式 之 模板模式

前文中,我們看了依賴注入怎幫助解耦合與提高擴展性。在文末,我們有講到,當你有很多長得很像的類別,大家做的事都差不多,但是彼此之間都差了一點點,這時候該怎麼辦?譬如我們拿一樣的殺手範例來看:

我們今天要做一個冷血殺手類別,這個殺手手上有一把手槍作為武器,每當要殺人時,他就會開槍,發出砰然巨響。
一個搞笑殺手類別,這個殺手手上不管有什麼,叫他殺人時他只會講笑話。
一個謹慎殺手類別,這個殺手做的事跟冷血殺手類似,但是做案完會記得清理現場。
一個見習殺手類別,這個見習殺手只會看,然後事成後幫忙清理現場。

不要急,我知道,你會說co一co很快,好好好,我當然知道你很快,但我們今天想要做的事,是要建立一個好的軟體工程範例,也就是要符合物件導向精神中的Don't Repeat Yourself的coding精神。要怎樣,才能將重複又類似的程式碼撿到做少呢?我們來看看 -- 『模板』設計模式吧。

首先我們還是要來重組需求,我自己的經驗,看到這種問題,一般會採如下的分析方法:

  1. 看起來,這些殺手都會收到同一個指令:『殺人』
  2. 每種殺手在殺人時,會採取的『動作』不一樣,但有部分相同
  3. 『動作』本身只有四種:『使用武器』、『講笑話』、『清理現場』,以及『看』

那我們就開始嚕,先把動作定義出來。這裡,我們不更動上文給的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都沒動喔!
https://ithelp.ithome.com.tw/upload/images/20171228/20107429VcaMTdfFXX.png

果真過了!(呼~好險)

為什麼會過?你看,我們只修改了CoolKiller的實踐方式,讓他去call模板的『使用武器』功能,跟他原本做的事其實一樣,當然會過啊!如果沒過,原因只可能有二:

  1. 你對需求理解有誤,趕快去找PO問清楚。
  2. 你敲程式碼時漏了一些邏輯,趕快檢查一下

自動測試的功用在這裡就顯現出來了。有了自動測試,我們在做這種路過幫修式的重構時,就比較有信心,才不會怕一個不小心又把正確的東西改做了而變得投鼠忌器。好的設計模式跟足夠信心的測試互相搭配,長期下來不管是程式品質或是擴展速度都是有幫助的。對了。你抓到重點了:

自動測試最大的幫助,不是在保證功能正確,而是『確保在增加新功能時不會破壞舊功能』

而,正如前一篇所說,設計模式五花八門各式各樣,各有其巧妙,也各有其適合的情境,還是得要多學習、多練習才能順手。


上一篇
『依賴注入(DI)』 -- DI做啥小?你才DI,你全家都DI!
下一篇
『真拿你沒辦法』 -- 現學現賣回顧會議工具箱
系列文
再戰軟體工程30

尚未有邦友留言

立即登入留言