大話設計模式之工廠三姐妹

   版權聲明:本文爲博主原創文章,未經博主允許不得轉載。

 工廠三姐妹一向受到了我們廣大工人階級的擁護。小妹簡單工廠模式,二姐工廠方法模式,大姐抽象工廠模式。爲了讓大家混淆,特意將這三種設計模式放到一起總結,開玩笑啦,放在一起總結是爲了方便比較異同。

簡單工廠模式

    簡單工廠就是幫我們來實例化對象的,當我們需要考慮用一個類來做這個創造實例的過程,這就是工廠。以一個簡單計算器爲例,有加、減、乘、除不同的算法,而且很可能會擴展開方、開根等其他運算,這時就可以考慮簡單工廠來幫我們創建。首先看一下計算器的代碼:

public class Operation{
    private double NumberA = 0;
    public double getNumberA()
    {
        return NumberA;
    }
    public void setNumberA(double numberA)
    {
        NumberA = numberA;
    }
    public double getNumberB()
    {
        return NumberB;
    }
    public void setNumberB(double numberB)
    {
        NumberB = numberB;
    }
    private double NumberB = 0;
    public double getResult(){
        double result = 0;
        return result;
    }
    
}

public class OperationAdd extends Operation
{

    @Override
    public double getResult()
    {
        double result = 0;
        result = numberA + numberB;
        return result;
    }
    
}

public class OperationSub extends Operation
{

    @Override
    public double getResult()
    {
        double result = 0;
        result = numberA - numberB;
        return result;
    }
    
}

public class OperationMul extends Operation
{

    @Override
    public double getResult()
    {
        double result = 0;
        result = numberA * numberB;
        return result;
    }
    
}

public class OperationDiv extends Operation
{

    @Override
    public double getResult()
    {
        double result = 0;
        if(numberB == 0)
        throw new Exception("除數不能爲0")
        result = numberA / numberB;
        return result;
    }
    
}

    上述代碼簡單實現了計算器的加減乘除功能,可能有人會問爲什麼要用繼承呢?直接在main函數裏寫一個switch不就搞定了嗎?有這樣想法的童鞋請去翻我上次總結的設計模式六大原則,首先,我們將運算邏輯和控制檯邏輯放在一起寫,就違反了單一職責原則,比如現在控制檯類型換了,以前的代碼是不能複用的,改需求就要更改大量的代碼就違反了開放-封閉原則。因此我們應該講運算邏輯和控制檯邏輯分開,降低代碼的耦合,這樣我們如果控制檯類型換了,運算邏輯的代碼是可以複用的,也便於維護。其次,我們應該講加減乘除單獨分開寫成類,分別繼承運算類,重寫getResult()方法來實現加減乘除同樣是滿足了單一職責原則(就一個類而言,應該僅有一個引起它變化的原因。如果一個類承擔的職責過多,就等於把這些職責耦合在一起,一個職責的變化可能會削弱或者抑制這個類完成其他職責的能力)。

    截止到現在我們任務就是在控制檯邏輯中去實例不同的實例來幫我們做加減乘除的運算。現在有一問題就是,目前只有四種運算,以後我們要是想增加開方、開根等運算的時候,這種易於變化的地方,應該考慮用一個單獨的類來做這個創造實例的過程,這就是工廠。代碼如下:

public class OperationFactory{
public static Operation creatOperate(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("+");
oper.NumberA = 1;
oper.NumberB = 2;
double result = oper.getResult();

    當我們增加一種新的運算的時候,只需要新寫一個OperationXX類繼承Operation並在修改工廠類即可(注意,對於開放封閉原則,不能考慮的過於絕對,我們要做的就是當需求改變的時候儘量用擴展解決問題,儘量減少更改)。


    以上是簡單工廠的UML類圖,至於類圖的學習就不放在這裏講了,大家查資料自學一下。

    同樣的功能要是二姐工廠模式來做的話,我們先看一下UML類圖:


    通過UML圖我們也能大概知道工廠模式的計算器代碼實現了,在此僅寫出客戶端代碼:

IFactory operFactory = new AddFactory();
Operation oper = operFactory.CreatOperation();
oper.numberA = 1;
oper.numberB = 2;
double result = oper.GetResult():

    前面講到小妹簡單工廠的時候說過,她的好處就是增加運算只需要增加一個類繼承Operation類,改一下工廠類中的case條件就可以了,更改工廠類還是違背了我們的開放封閉原則,那麼工廠模式呢?

工廠模式:定義一個用於常見對象的接口,讓子類決定實例化哪一個類。工廠方法使一個類的實例化延遲到其子類。

    在工廠模式中,當我們想增加運算的時候,需要加一個工廠類和一個運算類去實現工廠類,這樣整個工廠和產品體系其實都沒有修改的變化,而只是擴展的變化,這就完全符合了開放封閉原則的精神。

    其實二姐工廠模式是小妹簡單工廠的進一步抽象和推廣。在保持了簡單工廠模式優點的前提下,使得修改的代碼更少了。

    雖然二姐工廠模式在小妹的基礎上做了改進,但是當運算不斷增加,增加的類也會越來越多,增加了額外的開發量。那麼大姐抽象工廠會是什麼樣子呢?我們同樣是先來看一下大姐的UML圖。

    

    大姐已經是大人了,考慮問題已經和兩個妹妹不在一個維度上了,抽象工廠模式考慮的不是針對一個產品類而是針對一個產品系列。舉個例子,我們考慮一下數據庫的增刪改查操作,當我們需要更換數據庫,即將原有的SqlServer改爲Oracle,這時候如果是二姐和小妹的話可能是束手無策的,面對這麼多的表,只有大姐抽象工廠纔可以。比如User表和Department表等,那麼UML圖裏的PruduceA 1就是UserSqlserver,ProductB 1就是DepartmentSqlserver。AbstractProductA就是IUser,接口中聲明瞭對於user對象的增刪改查方法,同理AbstractProductA就是IDepartment。增加了表的話,比如增加一個Company表,我們需要在IFactory裏添加CreatCompany()方法,在工廠實現類中去實現。Factory 1的工廠實現其實就是SqlServer數據庫,Factory 2就是Oracle數據庫,它們針對User、Department、Company等這些表的增刪改查有着不同的實現。我們來看一下客戶端代碼:

User user = new User():
Department dept = new Department();
//IFactory factory = new SqlServerFactory();
IFactory factory = new OracleFactory();
IUser iu = factory.CreatUser();
iu.Insert(user);
IDepartment id = factory.CreatDept();
id.Insert(dept);

    當將SqlServer數據庫改成Oracle數據庫時,我們只需更改一行客戶端代碼即可實現。但是當我們沒增加一個表,就要增加產品抽象和產品實現,還要更改抽象工廠類和工廠實現類,這簡直是太糟糕了,我們需要近一步改進。

    此時呢,大家回想一下小妹簡單工廠的工廠類,裏面定義了一個字符串,通過switch的匹配來決定具體實現。代碼實現如下:

public class DataAccess{
private static final String db = "SqlServer";
//private static final String db = "Oracle";
public static IUser CreatUser(){
IUser result = null;
switch(db){
case "SqlServer":
result = new UserSqlServer();
break;
case "Oracle":
result = new UserOracle();
break;
}
return result;
}

public static IDepartment CreatDept(){
....
}
}

    客戶端代碼如下:

User user = new User();
Department dept = new Department();
IUser iu = DataAccess.CreatUser():
iu.Insert(User);
IDepartment id = DataAccess.CreatDept();
id.Insert(dept);
    到這裏,我們看到可以通過一個字符串去決定實際的數據庫訪問實例。這時大姐抽象工廠聽了表哥反射的建議,

採用反射技術代替我們上面的工廠類將數據的更換寫進配置文件,我們通過讀取配置文件中即可實現將程序由編譯時轉爲運行時。這裏可以總結的說:所有用到簡單工廠的地方,都可以考慮使用反射技術來去除switch或if,解除分支判斷帶來的耦合。

    由於篇幅限制,大姐抽象工廠闡述的並不是很具體,大家可以根據UML類圖自行寫代碼實現一下,會加深印象。具體的反射的實現,我們將在後序講解Spring源碼中具體去談到,敬請期待。文中如有問題還望大家能不吝賜教,共同學習進步。

    


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章