1. 程式人生 > >設計模式(六)工廠方法模式

設計模式(六)工廠方法模式

想想我們之前的簡單工廠模式,是這樣的:

結構圖:

工廠類:

class OperationFactory{
    public static Operation createOperate(string operate){
        Operation oper =null;
        switch(operate){
            case "+":
                oper=new OperationAdd();
                break;
            case "-":
                oper=new OperationSub();
                break;
            case "*":
                oper=new OperationMul();
                break;
            case "/":
                oper=new OperationDiv();
                break;
        }
        return oper;
    }
}

客戶端:

Operation oper ;
oper=OperationFactory.createOperate("+");
oper.NumberA=1;
oper.NumberB=2;
double 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();
    }
}

客戶端的實現是這樣的:

IFactory operFactory = new addFactory();
Operation oper = operFactory.CreateOperation();
oper.NumberA=1;
oper.NumberB=1;
double result=oper.GetResult();

問題來了,以前我們說過,如果我現在需要增加其他運算,比如求M數的N次方,或者求M數的N次方根,這些功能的增加,在簡單工廠裡,是先去加“求M數的N次方”功能類,然後去更改工廠方法,當中加“Case”語句來做判斷。現在用了工廠方法,加功能類沒問題,再加相關的工廠類,這也沒問題,但要我們再去更改客戶端,這不等於不但沒有減化難度,反而增加了很多類和方法,把複雜性增加了嗎?

其實,這就是工廠方法模式和簡單工廠的區別所在。簡單工廠模式的最大優點在於工廠類中包含了必要的邏輯判斷,根據客戶端的選擇條件動態例項化相關的類,對於客戶端來說,去除了與具體產品的依賴。就像之前的計算器,讓客戶端不用管該用哪個類的例項,只需要把“+”給工廠,工廠自動就給出了相應的例項,客戶端只要去做運算就可以了,不同的例項會實現不同的運算。但問題也就在這裡,如果要加一個“求M數的N次方”的功能,我們是一定需要給運算工廠類的方法里加“Case”的分支條件的。——修改原有的類?這就違背了開放-封閉原則

於是,工廠方法就來了——

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

結構圖:

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

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

考慮一個需求:薛磊風是一個大學生,以學雷鋒做好事的名義去幫助老人做事,現在們需要代替他去做好事,如何設計?

雷鋒類:

// 雷鋒
class LeiFeng{
    public void Sweep(){
        Console.WriteLine("掃地");
    }
    public void Wash(){
        Console.WriteLine("洗衣");
    }
    public void BuyRice(){
        Console.WriteLine("買米");
    }
        
}

學雷鋒的大學生類,繼承雷鋒:

// 學雷鋒的大學生
class Undergraduate:LeiFeng{}

客戶端:

LeiFeng xueleifeng =new =Undergraduate();

xueleifeng.BuyRice();
xueleifeng.Sweep();
xueleifeng.Wash();

現在假設你們有三個人要去代替他做這些事,該怎麼寫?

似乎應該例項化三個“學雷鋒的大學生”物件?

LeiFeng student1 = new Undergraduate();
student1.Buyrice();

LeiFeng student2 = new Undergraduate();
student2.Sweep();

LeiFeng student3 = new Undergraduate();
student3.Wash();

大學生都是要畢業的,而幫助老人卻是長期工作,所以“社群志願者”更合適。此時這樣的寫法就非常不合適了,因為我們需要更改多個例項化的地方。 

增加一個繼承“雷鋒”類的“社群志願者”類: 

// 社群志願者
class Volunteer : LeiFeng{}

再寫簡單工廠類:

// 簡單雷鋒工廠
class SimpleFactory{
    public static LeiFeng CreateLeiFeng(string type){
        LeiFeng result = null;
        switch(type){
            case "學雷鋒的大學生":
                result = new Undergraduate();
                break;
            case "社群志願者":
                result = new Volunteer();
                break;
        }
        return result;
    }
}

客戶端的程式碼,如果要換,就只需換“學雷鋒的大學生”為“社群志願者”

// 簡單工廠模式
LeiFeng studentA = SimpleFactory.CreatedLeiFeng("學雷鋒的大學生");
studentA.BuyRice();
LeiFeng studentB = SimpleFactory.CreatedLeiFeng("學雷鋒的大學生");
studentB.Sweep();
LeiFeng studentC = SimpleFactory.CreatedLeiFeng("學雷鋒的大學生");
studentC.Wash();

// 三句重複的程式碼

然後你會發現,你需要在任何例項化的時候寫出這個工廠的程式碼。這裡有重複,也就有了壞味道。

再用工廠方法模式寫一遍?

// 雷鋒工廠
interface IFactory{
    LeiFeng CreateLeiFactory();
}

// 學雷鋒的大學生工廠
class UndergraduateFactorry : IFactory{
    public LeiFeng CreateLeiFeng(){
        return new Undergraduate();
    }
}

// 社群志願者工廠
class VolunteerFactory:Ifactory{
    public LeiFeng CreateLeiFeng(){
        return new Volunteer();
    }
}

客戶端呼叫:

// 工廠方法模式
IFactory factory = new UndergraduateFactory();
LeiFeng student =factory.CreateLeiFeng();    // 要換成"社群志願者",修改這裡就可以
    
student.BuyRice();
student.Sweep();
student.Wash();

總結一下——

工廠方法克服了簡單工廠違背開放-封閉原則的缺點,又保持了封裝物件建立過程的優點。工廠方法模式是簡單工廠模式的進一步抽象和推廣。由於使用了多型性,工廠方法模式保持了簡單工廠模式的優點,而且克服了它的缺點。但缺點是由於每加一個產品,就需要加一個產品工廠的類,增加了額外的開發量。

那麼,有沒有最佳做法呢?

哈,其實,利用“反射”可以解決避免分支判斷的問題!反射是什麼?別急,接下來,我們會慢慢講到......

本章完。

------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 本文是連載文章,此為第六章,學習遵循對簡單工廠模式進行改進的工廠方法模式。

下一章:

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------