1. 程式人生 > >設計模式必知必會:三種工廠方法之對比

設計模式必知必會:三種工廠方法之對比

在開發中,有沒有試過使用工廠方法呢,不同的工廠方法之間又有什麼不同呢,今天就來好好講一講。本文假設讀者都已經瞭解了三種工廠方法,所以對三種工廠方法的細節不再贅述。
首先我們總共有三種工廠:

  1. 簡單工廠模式
  2. 工廠方法模式
  3. 靜態工廠模式

不同之處

有什麼不同?看程式碼:

簡單工廠模式

class Father{
}

class ChildA extends Father{
}

class ChildB extends Father{
}

class ChildFactory {
    public Father IWantChild(char type){
        switch
(type){ case 'A':return new ChildA(); case 'B':return new ChildB(); } return null; } }

是不是很清晰,我們只要呼叫工廠的IWantChild方法,就可以實現創造出不同的子類,使用起來非常方便。

工廠方法模式

在來看看工廠方法模式:

class Father{
}

class ChildA extends Father{
}

class ChildB extends Father{
}

interface IFactory
{
public Father IWantChild(); } class AFactory implements IFactory{ @Override public Father IWantChild() { return new ChildA(); } } class BFactory implements IFactory{ @Override public Father IWantChild() { return new ChildB(); } }

簡單工廠模式 對比 工廠方法模式

看上去兩種工廠方法沒有什麼不同呀,為什麼要這麼使用呢?讓我們來細細思考,從定義上看似乎沒有什麼問題,現在是隻有ChildA和ChildB兩個,假如現在我們需要ChildC了,該怎麼改呢?
對於簡單工廠模式,我們需要做什麼,沒錯,就是去對工廠ChildFactory的IWantChild方法進行修改,新增一個新的分支,同時新建一個新的ChildC類並且這個類繼承於Father類,不難對吧,但是如果我們是寫庫給別人用,且不希望別人修改我們的程式碼,那麼別人要如何去生成新的ChildC類呢,難道還是使用new ChildC嗎,這就非常不好了。
對於工廠方法模式,我們需要做什麼呢,簡單,繼承IFactory實現ChildC的工廠,同時繼承Father,實現新的ChildC類,使用上非常方便,且不需要去修改原有的程式碼。很好。

靜態工廠方法

程式碼如下:

class Father{
}

class ChildA extends Father{
}

class ChildB extends Father{
}

class ChildFactory{
    public static Father IWantChildA(){
        return new ChildA();
    }

    public static Father IWantChildB(){
        return new ChildB();
    }
}

看起來感覺上是不是很多餘,相對於工廠方法模式,似乎還少了一些可擴充套件性,但是別忘了,靜態工廠的使用場景與上面兩種工廠方法的使用場景是不完全相同的,在特定的使用場景下,相信你也會更傾向於使用靜態工廠的。
不管這些,從上面的程式碼中,靜態工廠有什麼優勢?第一、我們可以返回任何子型別的物件;第二、我們在建立引數化的例項時,程式碼更加簡潔,例如上面的,只需要呼叫靜態工廠方法,而不需要去填寫引數;另外還有呢?對的,有名字,這個特點比起工廠方法似乎沒什麼優勢,這是因為上面這種寫法不夠直觀,看看下面這種寫法:

class Child {
    private char ch;

    private Child(char ch){
        this.ch = ch;
    }

    public static Child ChildA(){
        return new Child('A');
    }

    public static Child ChildB(){
        return new Child('B');
    }
}

看,這種寫法可以有效的按照要求生成類,同時我們可以隱藏我們的構造器,讓外界的呼叫只能基於靜態工廠,而不能呼叫到我們的構造器,安全性更好,且這種寫法不需要每次都建立新的物件,讓物件的建立都掌握在我們的手中:

class Child {
    private char ch;
    private static Child child = null;

    private Child(){
    }

    public static Child ChildA(){
        if(child == null){
            child = new Child();
        }
        child.ch = 'A';
        return child;
    }

    public static Child ChildB(){
        if(child == null){
            child = new Child();
        }
        child.ch = 'B';
        return child;
    }
}

但是靜態工廠方法有沒有什麼侷限呢?看上面這段程式碼就明白了,如果構造器不是公有的或者可繼承的,那麼我們就無法建立子類。
另外還有一個極其嚴重的例項,假如使用者在新建這個類例項的時候,需要傳遞30個引數,那麼我們該怎麼辦?當然30個是有點多了,也正好可以說明問題,傳遞30個引數,很容易就導致引數的傳遞錯誤,非常不好的體驗,尤其是幾個引數型別什麼的都一樣,僅僅是功能不同,那麼就會導致程式無法執行出正確的結果。
有沒有解決辦法?

Builder模式
class Child {
    private char ch;
    private Child(char ch){
        this.ch = ch;
    }

    public static class Builder{
        private char ch;

        public Builder setCh(char ch){
            this.ch = ch;
            return this;
        }

        public Child build(){
            return new Child(ch);
        }
    }
}

如上所示,我們只向外界暴露我們的建造器Builder,Builder通過set方法設定引數,這樣我們就可以通過set的名字來相應的設定不同的引數,最後在Builder中統一呼叫建構函式進行返回,就不容易出錯了。同時,我們也可以在set的時候進行判斷和提示,在build中根據引數的不同返回子類,使用上非常方便。有沒有問題?有的,問題還是有的,在我們的程式碼中,就相當於需要兩個ch,如果引數很多很長,且引數佔用的空間比較大,那麼就容易導致浪費。不過這種模式還是很常用的。

後記

本文比較了三種工廠方法,對不同的工廠方法的使用做了一定的說明,還是那句話,根據自己的應用場景來進行選擇,適合的才是最好的。