設計模式之簡單工廠模式

設計模式之簡單工廠模式
動機:
        不暴露實例化邏輯來創建對象。通過公共的接口創建新的對象。
        這是一個簡單的實現,客戶端需要一個product,但是client不直接使用new對象,而是通過提供需要的對象信息來找factory得到新的product。
        這個factory實例化一個具體的product並返回(轉化成抽象的類),client段使用這個抽象的類而不用考慮它具體的實現。

應用舉例:        也許工廠模式是使用最多的模式之一。舉個例子,一個操作圖像的圖形應用。在我們實現繪畫的模塊就是client,而那些shapes圖形就是products,所有的圖形都繼承自一個abstract shape抽象的形狀類(或者接口)。 這個abstract shape定義了繪畫draw和move方法去讓子類實現。讓我們假設有個創建圓形的命令,client接收一個string類型的圖形類型參數,client從factory得到concrete shape轉換成abstract shape。
        這樣的好處是很顯然的:新增一個圖形不用修改client的代碼,只要在factory的實現裏面增加新的類型即可。
詳細問題和解決方法:
        新手使用switch/case參數化factory,生成product的方法可以被重寫,所以factory可以生成更多類型的product對象,使用一個條件(輸入一個方法參數或者一些全局配置參數)頭標識哪種product對象應該被創建。代碼如下:
public class ProductFactory{
    public Product createProduct(String ProductID){
        if (id==ID1)
            return new OneProduct();
        if (id==ID2) return
            return new AnotherProduct();
        ... // so on for the other Ids
        return null; //if the id doesn't have any of the expected values
    }
    ...
}
        這種實現方法是簡單直接的(讓我們稱之爲新手實現),這裏的問題就是,新增一種product我們就要修改factory類,不夠彈性,違反了開閉原則。當然我們可以繼承factory類,但是別忘了factory類通常是單例的。
註冊類-使用反射reflection:
        如果你可以使用反射,就可以在不修改factory的前提下注冊新的product類。在不知道product類型的前提下我們在facoty中創建product對象,我們可使用map保存productID和product type,在這種情況下,新增一個新的product,需要在map中註冊該product,這個操作不需要修改factory的代碼:
class ProductFactory
{
    private HashMap m_RegisteredProducts = new HashMap();
    public void registerProduct (String productID, Class productClass)
    {
        m_RegisteredProducts.put(productID, productClass);
    }
    public Product createProduct(String productID)
    {
        Class productClass = (Class)m_RegisteredProducts.get(productID);
        Constructor productConstructor = cClass.getDeclaredConstructor(new Class[] { String.class });
        return (Product)productConstructor.newInstance(new Object[] { });
    }
}
        我們可以把registion code註冊代碼放在任何地方,但是使用static靜態代碼塊在product類裏面註冊是一個方便的方法,看下面的例子:
1、在product類外面註冊:
public static void main(String args[]){
        Factory.instance().registerProduct("ID1", OneProduct.class);
    } 
2、在product類裏面註冊:
class OneProduct extends Product
{
	static {
		Factory.instance().registerProduct("ID1",OneProduct.class);
	}
	...
}        我們必須確保在factroy使用concrete product具體的product之前完成註冊,不然就會出現null point空指針,我們在Main類的static靜態代碼塊中使用Class.forName方法。Class.forName方法支持返回一個類的實例,如果一個編譯器沒有加載這個類,編譯器就會在Class.forName被調用的時候加載。
class Main
{
    static
    {
        try
        {
            Class.forName("OneProduct");
            Class.forName("AnotherProduct");
        }
        catch (ClassNotFoundException any)
        {
            any.printStackTrace();
        }
    }
    public static void main(String args[]) throws PhoneCallNotRegisteredException
    {
        ...
    }
}
        這種反射實現有它的壞處,一個主要的壞處就是它的性能表現,使用反射要比不使用反射性能表現降低10%。
註冊類-不使用反射:
        在之前的段落中我們看到facotory使用map保存productID和product type,這個註冊器在factory外面實現,因爲通過使用反射不用再關心所要創建對象的類型。
        我們不想使用反射,但是同時factory又不需要考慮product類型。我們在product abstract class裏面新增一個新的abstract方法,每個具體的類都要實現設個方法去創造自己。我們也不得不改變註冊器這樣我們將註冊具體的product對象。
abstract class Product
{
    public abstract Product createProduct();
    ...
}
class OneProduct extends Product
{
    ...
    static
    {
        ProductFactory.instance().registerProduct("ID1", new OneProduct());
    }
    public OneProduct createProduct()
    {
        return new OneProduct();
    }
    ...
}
class ProductFactory
{
    public void registerProduct(String productID, Product p)    {
        m_RegisteredProducts.put(productID, p);
    }
    public Product createProduct(String productID){
        ((Product)m_RegisteredProducts.get(productID)).createProduct();
    }
}
一個更高級的解決方法-使用抽象工廠abstract factory(工廠方法 factory method):
    這種實現代表一個可修改的註冊器類實現,假設我們新增一個新的product,使用過程化的switch/case我們需要修改factory類的代碼,而使用註冊器類,我們要做的只是把註冊器類給factory,而不用修改factory,這肯定是一個彈性的解決方式。
    過程化的實現方式是典型的違背開閉原則的壞例子,我們能看到有很多擴展factory的直接解決方法去避免修改factory。
工廠方法模式factory method pattern經典的實現方式有一些壞處通過註冊器,而沒有很多的好處:
 好處:當product對象被創建的時候,繼承的factory method可以被修改去表現添加的操作。
壞處:
    1、factory必須要用singleton實現。
    2、每個factory必須要在使用前先實例化。
    3、實現起來有更多的困難。
    4、如果一個新的product要被創建,一個新的factory要被創建。
不管怎麼樣,經典的實現方式有它的好處,可以幫助我們理解抽象工廠模式abstract factory pattern。
結論:        當你設計一個應用的時候,考慮你是否需要使用factory,也許使用factory會帶來沒必要的複雜。如果你有很多類有相同的基本類型,你就需要使用factory,如果你有許多如下的代碼,你就要重新考慮:
(if (ConcreteProduct)genericProduct typeof )
    ((ConcreteProduct)genericProduct).doSomeConcreteOperation().
        如果你決定使用factory模式,我會推薦你使用帶註冊器的實現(使用反射或者不使用反射都行),避免使用factory method,記住switch/case是最簡單,違反開閉原則,只是用來解釋factory原理的。


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