工廠方法模式 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();
}