Home [閱讀筆記][Design Pattern] Ch8.工廠方法模式 Factory Method
Post
Cancel

[閱讀筆記][Design Pattern] Ch8.工廠方法模式 Factory Method

工廠方法模式 Factory Method

工廠方法模式 (Factory Method):定義一個用於建立物件的介面,讓子類決定實體化哪一個類別。工廠方法使一個類別的實例化延遲到其子類別。

優點

由於使用了多型性,工廠方法模式保持了簡單工廠模式的優點。

缺點

缺點是:由於每加一個產品,就需要加一個產品工廠的類別,增加了額外的開發量。

怎樣才能避免修改用戶端的程式碼?利用「反射」可以解決避免分支判斷的問題。

工廠方法結構圖

  • Product工廠類別:定義工廠方法所建立的物件的介面
  • ConcreteProduct具體工廠類別:具體的產品,實現了Product介面
  • Creator:宣告工廠方法,該方法返回一個Product類型的物件
  • ConreteCreator:重定義工廠方法以返回一個ConreteCreator實例
1
2
3
4
5
6
7
Product :定義工廠方法所建立的物件的介面
    ConcreteProduct :具體的產品,實現了Product介面

Creator :宣告工廠方法,該方法返回一個Product類型的物件
+ FactoryMethod()
    ConreteCreator : 重定義工廠方法以返回一個ConreteCreator實例
    + FactoryMethod()

簡單工廠 vs. 工廠方法

「工廠方法」克服了「簡單工廠」違背「開放-封閉原則」的缺點,又維持了封裝物件建立過程的優點。

它們都是集中封裝了物件的建立,使得要更換物件時,不需要做大的改動就可實現,降低了客戶程式與產品物件的耦合。
「工廠方法模式」是「簡單工廠模式」的進一步抽象和推廣。

簡單工廠

簡單工廠」的最大優點在於:工廠類別中包含了必要的邏輯判斷,根據用戶端的選擇條件動態實體化相關的類別,對於用戶端來說,去除了與具體產品的依賴。

就像計算機,讓用戶端不用管該用哪個類別的實體,只需要把+給工廠,工廠自動就給出了相應的實體,用戶端只要去做運算就可以了,不同的實體會實現不同的運算。

但問題也就在這裡,如果要加一個「求M數的N次方」的功能,我們是一定需要給運算工廠類別的方法裡加Case的分支條件,修改原有的類別。

這可不是好辦法,這就等於說,我們不但對擴展開放了,對修改也開放了,這就違背了「開放-封閉原則

違背了「開放-封閉原則」,於是「工廠方法」就來了。

工廠方法

「工廠方法模式」實現時,用戶端需要決定實體化哪一個工廠出來實現運算類別,選擇判斷的問題還是存在的,也就是說,「工廠方法」把「簡單工廠」的內部邏輯判斷移到了用戶端程式碼來進行。你想要加功能,本來是改工廠類別的,而現在是修改用戶端!

這樣整個工廠和產品體系都沒有修改的變化,而只是擴展的變化,這就完全符合了「開放-封閉原則」。

簡單工廠模式實現

在「簡單工廠模式」裡,如果需要增加其他的運算,比如「M數的N次方」,就必須先去加「M數的N次方」類別,然後去工廠類別的方法裡加case的分支條件判斷。(違背了「開放-封閉原則」)

結構圖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
簡單工廠類別
+ CreateOperate(in operate:string)

運算類別(抽象類別)
+ NumberA:decimal
+ NumberB:decimal
+ GetResult():decimal

    加法類別
    + GetResult()
    減法類別
    + GetResult()
    乘法類別
    + GetResult()
    除法類別
    + GetResult()

程式碼實現

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
//用戶端
Operation oper;
oper = OperationFactory.CreateOperate("+");
oper.NumberA = 10;
oper.NumberB = 20;
decimal result = oper.GetResult();

//簡單工廠類別
class OperationFactory {
    public static Operation CreateOperate(string operate) {
        Operation oper = null;
        switch(operate) { //如果要增加新功能運算「M數的N次方」,就必須修改這方法,加上case分支,這違背了「開放-封閉原則」
            case "+":
                oper = new OperationAdd();
            break;
            case "-":
                oper = new OperationSub();
            break;
            case "*":
                oper = new OperationMul();
            break;
            case "/":
                oper = new OperationDiv();
            break;
        }
        return oper;
    }
}
//運算類別
abstract class Operation {
    public decimal NumberA {get; set;}
    public decimal NumberB {get; set;}
    public abstract decimal GetResult();
}
//加法類別
class OperationAdd:Operation {
    public override decimal GetResult() {
        return NumberA + NumberB;
    }
}
//減法類別
class OperationSub:Operation {
    public override decimal GetResult() {
        return NumberA - NumberB;
    }
}
//乘法類別
class OperationMul:Operation {
    public override decimal GetResult() {
        return NumberA * NumberB;
    }
}
//除法類別
class OperationDiv:Operation {
    public override decimal GetResult() {
        return NumberA / NumberB;
    }
}

工廠方法模式實現

「簡單工廠」的工廠類別與判斷分支耦合,那麼就對它下手,根據「依賴倒轉原則」,我們把工廠類別抽象出一個介面,這個介面只有一個方法,就是建立抽象產品(加減乘除)的工廠方法,所有要生產具體類別的工廠,就去實現這個介面,這樣,一個簡單工廠模式的工廠類別,改成了一個工廠抽象介面和多個具體產生物件的工廠,於是我們要增加「求M數的N次方」功能時,就不需要更改原有的工廠類別,只需要增加此功能的的運算類別和相應的工廠類別就可以了。

結構圖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
工廠類別(介面)
+ CreateOperate():運算類別

    加法工廠
    + CreateOperation():加法類別
    減法工廠
    + CreateOperation():減法類別
    乘法工廠
    + CreateOperation():乘法類別
    除法工廠
    + CreateOperation():除法類別

運算類別(抽象方法)
+ NumberA:decimal
+ NumberB:decimal
+ GetResult():decimal

    加法類別
    + GetResult()
    減法類別
    + GetResult()
    乘法類別
    + GetResult()
    除法類別
    + GetResult()

工廠方法程式碼實現

工廠方法模式實現時,用戶端需要決定實體化哪一個工廠來實現運算類別,選擇判斷的問題還是存在 ,也就是說,工廠方法把簡單工廠的內部邏輯判斷移到了用戶端程式碼來進行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
//用戶端
IFactory operFactory = new AddFactory(); //這裡修改加減乘除(這裡決定實體化哪一個工廠來實現運算類別)
Operation oper = operFactory.CreateOperation();
oper.NumberA = 20;
oper.NumberB = 10;
decimal result = oper.GetResult();

//工廠介面
interface IFactory {
    Operation CreateOperation();
}

//加減乘除各建一個具體工廠去實現這個介面
class AddFactory:IFactory { //加法類別工廠
    public Operation CreateOperation() {
        return new OperationAdd();
    }
}
class SubFactory:IFactory { //減法類別工廠
    public Operation CreateOperation() {
        return new OperationSub();
    }
}
class MulFactory:IFactory { //乘法類別工廠
    public Operation CreateOperation() {
        return new OperationMul();
    }
}
class DivFactory:IFactory { //除法類別工廠
    public Operation CreateOperation() {
        return new OperationDiv();
    }
}

//運算類別
abstract class Operation {
    public decimal NumberA{ get; set;}
    public decimal NumberB{ get; set;}
    public abstract decimal GetResult();
}
//加減乘除各建一個具體的運算去實現這個介面
class OperationAdd:Operation {
    public override decimal GetResult() {
        return NumberA + NumberB;
    }
}
class OperationSub:Operation {
    public override decimal GetResult() {
        return NumberA - NumberB;
    }
}
class OperationMul:Operation {
    public override decimal GetResult() {
        return NumberA * NumberB;
    }
}
class OperationDiv:Operation {
    public override decimal GetResult() {
        return NumberA / NumberB;
    }
}

南丁格爾工廠

學習南丁格爾的名義做好事。

南丁格爾類別

「南丁格爾類別」擁有掃地、洗衣、買米等方法。

1
2
3
4
5
6
7
8
9
10
11
12
//南丁格爾類別
class Nightingale {
    public void Sweep() {
        Console.WriteLine("掃地");
    }
    public void Wash() {
        Console.WriteLine("洗衣");
    }
    public void BuyRice() {
        Console.WriteLine("買米");
    }
}

學南丁格爾的大學生

繼承南丁格爾類別

1
class Undergrad:Nightingale { }

用戶端程式碼

1
2
3
4
Nightingale Rii = new Undergrad();
Rii.BuyRice();
Rii.Sweep();
Rii.Wash();

如果有三個人要代他做這些事,就要實體化三個Undergrad物件了…

社區義工類別

如果大家都畢業了,而幫助老人是長期工作,所以「社區義工」更適合,受幫助的人不需要知道是誰來做好事,他只需知道是學南丁格爾的人來幫忙,所以需增加一個繼承「南丁格爾」的「社區義工」類別。

1
class Volunteer:Nightingale { }

簡單工廠模式

寫個簡單工廠類別

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class SimpleFactory {
    public static Nightingale CreateNightingale(string type) {
        Nightingale result;
        switch(type) {
            case "大學生":
                result = new Undergrad();
            break;
            case "社區義工":
                result = new Volunteer();
            break;
        }
        return result;
    }
}

用戶端調用

簡單工廠模式,你需要實體化這個工廠的時候,就會有三句重複的程式碼,有重複,就是有壞味道。

1
2
3
4
5
6
7
8
9
10
public static void Main()
{
    //簡單工廠模式,這裡就會發現有三句重複的程式碼
    Nightingale A = SimpleFactory.CreateNightingale("大學生");
    Nightingale B = SimpleFactory.CreateNightingale("大學生");
    Nightingale C = SimpleFactory.CreateNightingale("大學生");
    A.BuyRice();
    B.Sweep();
    C.Wash();
}

工廠方法模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//南丁格爾工廠(介面)
interface IFatory {
    Nightingale CreateNightingale();
}

//大學生工廠
class UndergradFatory:IFatory {
    public Nightingale CreateNightingale() {
        return new Undergrad();
    }
}

//社區義工工廠
class VolunteerFatory: IFatory {
    public Nightingale CreateNightingale() {
        return new Volunteer();
    }
}

//南丁格爾類別
class Nightingale {
    public void Sweep() {
        Console.WriteLine("掃地");
    }
    public void Wash() {
        Console.WriteLine("洗衣");
    }
    public void BuyRice() {
        Console.WriteLine("買米");
    }
}
//大學生
class Undergrad: Nightingale { }
//社區義工
class Volunteer: Nightingale { }

用戶端調用

儘管如果要換成「社區義工」也還是要修改程式碼,但是只需要修改一處就可以了。

1
2
3
4
5
6
7
8
9
10
11
public static void Main()
{
    IFatory fatory = new UndergradFatory(); //要換成「社區義工」,只要修改這裡就好了
    Nightingale studentA = fatory.CreateNightingale();
    Nightingale studentB = fatory.CreateNightingale();
    Nightingale studentC = fatory.CreateNightingale();
    
    studentA.BuyRice();
    studentB.Sweep();
    studentC.Wash();
}
This post is licensed under CC BY 4.0 by the author.

[閱讀筆記][Design Pattern] Ch7.代理模式 Proxy

[閱讀筆記][Design Pattern] Ch9.原型模式 Prototype (深淺複製 MemberwiseClone())