由淺到深瞭解工廠模式

天星技術團隊發表於2018-09-26

作者 點先生 日期 2018.9.26

嘮個嗑

先給各位觀眾老爺道個歉,在上一篇文章的末尾本來說了這次要給大家分享代理模式,但是臣妾,做不到啊! 最近公司給我了一個新專案,於是比較忙一點,再加上代理模式那邊的東西有點多,我有點懵逼的,靜態、動態、遠端、虛擬,還有個RMI,小機靈鬼兒的腦袋一時間處理不過來啊!

由淺到深瞭解工廠模式
最近在搭建新專案的時候,參考了前輩的一些程式碼。這一次看別人程式碼的時候,更容易知道別人寫著類的目的是幹嘛,為啥要這樣寫了,這就是學習設計模式之後的好處之一吧,我仍然會繼續加油。嚐到了一些甜頭,現在更有動力了。你們的留言,討論,點贊更是我巨大的動力。

雖然是中途改道來寫工廠模式,但絕對不會讓各位觀眾老爺失望的!本次要講的是三種工廠模式(簡單工廠模式,工廠方法模式,抽象工廠模式),以及相關模式原始碼上的一些理解、擴充套件。

什麼是工廠模式

new!
準確的說,是代替new例項化具體類的一種模式。 接下來我將以“音樂廠牌創造音樂”為例子,由淺到深深入工廠模式。
至於為什麼要用工廠模式我會邊講例子邊說。

簡單工廠模式

製作一首歌曲,確定歌曲風格之後,就先要寫詞譜曲,然後依次就是錄歌,剪輯,混音,就可以髮型了。當然也可以“不混,直接發”!Skr~。

public class MusicLabel {
    Song createSong(String type){
        Song song = null;

        if(type.equals("folk")){ 
            song = new FolkSong();
        }else if(type.equals("rock")){
            song = new RockSong();
        }else if(type.equals("pop")){
            song = new PopSong();
        }

        song.prepare();//作詞作曲演奏
        song.Sing(); //錄歌
        song.Cut(); //剪輯
        song.Mix(); //混音
        return song;
    }
}
複製程式碼

這樣寫,有沒有問題? 沒有! 不出bug能跑就完事兒了。科科。
然而這樣卻違反了開閉原則:對擴充套件開放,對修改關閉
我們可以把易變化的部分跟不變化的部分分開。也就是將new物件的部分提出來,單獨形成一個類(工廠)。

public class SongFactory {
    public Song orderSong(String type){
        Song song = null;
        if(type.equals("folk")){
            song = new FolkSong();
        }else if(type.equals("rock")){
            song = new RockSong();
        }else if(type.equals("pop")){
            song = new PopSong();
        }
        return song;
    }
}
複製程式碼

在這兒有另外一種方法:利用靜態方法定義一個簡單工廠(靜態工廠)。
這樣就不需要使用建立物件的方法來例項化物件。但這樣也有一個缺點:不能通過繼承來改變建立方法的行為。

修改之後重寫MusicLabel類

public class MusicLabel {
    SongFactory factory;
    public MusicLabel(SongFactory factory) {
        this.factory = factory;
    }
    Song createSong(String type){
        Song song = null;
        song = factory.orderSong(type);
        song.prepare();
        song.Sing();
        song.Cut();
        song.Mix();
        return song;
    }
}
複製程式碼

這樣一來就將面向具體程式設計,變成了面向介面程式設計。

在設計模式中,“實現一個介面”泛指“實現某個超型別(類/介面)的某個方法”。

心得

給我的感覺,簡單工廠模式更像是一種程式設計的習慣。最簡單的解耦,使得工廠類能夠被各種廠牌反覆使用。
在我還沒認識簡單工廠之前,其實我就寫過很多簡單工廠的例子了。各種基類BaseActivity、BaseFragment等等通常都會用到簡單工廠模式。
優點: 簡單,解耦。
缺點: 靜態工廠無法繼承,違反開閉原則。

工廠方法模式

定義

工廠方法模式定義了一個建立物件的介面,但由子類決定要例項化的類是哪一個。工廠方法讓類把例項化推遲到子類。

類圖

由淺到深瞭解工廠模式

工廠方法模式有四個核心類:

  1. Product(產品類):所有產品必須實現這個共同的介面,這樣使用這些產品的類就可以引用這個介面,而不是具體類。
  2. ConcreteProduct(具體產品類)
  3. Creator(建立者類):實現了所有操縱產品的方法,但不實現工廠方法。
  4. ConcreteCreator(具體建立者類):實現了factoryMethod(),負責建立一個或多個具體產品,只有ConcreteCreator類知道如何建立這些產品。

來擼程式碼

剛剛我們已經建立了兩個類,MusicLabel和SongFactory,MusicLabel在工廠方法中可以作為一個Creator。SongFactory不在四大核心之內,先不管。
我們先來創造一下產品類和建立者類(他們是兩個平行類層級)。

public abstract class MusicLabel {
    Song createSong(String type){
        Song song = null;
        song = orderSong(type);
        song.prepare();
        song.Sing();
        song.Cut();
        song.Mix();
        return song;
    }
    abstract Song orderSong(String type); //工廠方法
}
複製程式碼
public abstract class Song {
    String singer;//演唱者
    String lyricist;//作詞人
    String composer;//作曲人
    String prepare() {
        return "演唱者:"+singer + ",作詞人:"+lyricist + ",作曲人:"+composer;
    }
    String Sing(){
        return "錄歌";
    }
    String Cut(){
        return "剪下";
    }
    String Mix(){
        return "混音";
    }
}
複製程式碼

接下來建立各自的子類。廠牌方面,各位最熟知的可能就是“摩登天空”了,另外,聽國搖的小夥伴對謝天笑這個名字應該不會陌生,謝天笑是在“十三月”音樂廠牌。這裡我們就以這兩個廠牌為例,來寫各自的子類。

摩登天空音樂廠牌

public class MDSkyMusicLabel extends MusicLabel {
    @Override
    Song orderSong(String type) { //此處可用簡單工廠模式
        if(type.equals("folk")){
            return new MDSkyFolkSong();
        }else if(type.equals("rock")){
            return new MDSkyRockSong();
        }else if(type.equals("pop")){
            return new MDSkyPopSong();
        }else return  null;
    }
}
複製程式碼

十三月音樂廠牌

public class ThirteenMonthMusicLabel extends MusicLabel {
    @Override
    Song orderSong(String type) { //此處可用簡單工廠模式
        if(type.equals("folk")){
            return new ThirteenMonthFolkSong();
        }else if(type.equals("rock")){
            return new ThirteenMonthRockSong();
        }else if(type.equals("pop")){
            return new ThirteenMonthPopSong();
        }else return  null;
    }
}
複製程式碼

在MusicLabel類的createSong()中,並不知道真正建立的是哪一個廠牌的音樂。建立具體物件的工作,都在子類中。
接下來的工作就是把剛剛寫過的MDSkyFolkSong等具體子類繼承Song。這裡只寫一個。

public class MDSkyFolkSong extends Song {
    public MDSkyFolkSong() {
        singer = "摩登天空的民謠藝人";
        lyricist = "摩登天空的民謠作詞人";
        composer = "摩登天空的民謠作曲人";
    }
}
複製程式碼

在這裡或許許多小夥伴要說這樣寫會有很多子類,很麻煩。但這樣已經是最優的選擇了。耦合度低,遵守了開閉原則。

感受

工廠方法模式有點像簡單工廠的合集,特別是當只有一個具體工廠類存在時。
簡單工廠可以將物件的建立封裝起來,但是簡單工廠不具備工廠方法的彈性,因為簡單工廠不能變更正在建立的產品。
優點: 在簡單工廠的優點上加上“可以變更正在建立的產品”。
缺點: 子類相當多,不便於管理。

抽象工廠模式

剛剛我們再寫具體廠牌的時候,有提到,可以在具體廠牌類中使用簡單工廠模式。也就是說,我們可以建立MDSkySongFactory和ThirteenMonthSongFactory兩個工廠類。並且這兩個工廠做的事都是一樣的,只是具體東西不一樣而已。
那……
我們是不是可以寫一個工廠超類,把要做的事情寫成抽象方法,再讓子工廠類各自實現呢?
可以的!這就是傳說中的抽象工廠模式。

定義

為建立一組相關或相互依賴的物件提供一個介面,而且無需指定他們的具體類。

類圖

這圖畫的好辛苦

  1. Cilent(客戶類):程式碼中只需涉及抽象工廠,執行時自動使用實際的工廠。
  2. AbstractFactory(抽象工廠):定義了一個介面,所有具體工廠必須實現它。這個介面包含了一組方法來生產產品。
  3. ConcreteFactory(具體工廠):客戶只需要使用其中一個工廠而不需要例項化任何產品物件。
  4. AbstractProduct(抽象產品類):這些抽象產品類就是抽象工廠中所需要的每一個條件。
  5. ConcreteProduct(具體產品類):繼承抽象產品類。

由入門到放棄

剛剛我們說過,我們可以整理一個工廠超類,這個工廠超類,就是AbstractFactory!它在我們這個例子中的作用就是返回一個singer,一個lyricist和一個composer。所以我們可以這樣寫。

public interface SongFactory {
    public String findSinger();
    public String findLyricist();
    public String findComposer();
}
複製程式碼

然後給每個廠牌都寫一個具體工廠

public class MDskySongFactory implements SongFactory {
    @Override
    public String findSinger() {
        return new MDskySinger();
    }
    @Override
    public String findLyricist() {
        return new MDskyLyricist();
    }
    @Override
    public String findComposer() {
        return new MDskyComposer();
    }
}
複製程式碼
public class ThirteenMonthSongFactory implements SongFactory {
    @Override
    public String findSinger() {
        return new ThirteenMonthSinger();
    }
    @Override
    public String findLyricist() {
        return new ThirteenMonthLyricist();
    }
    @Override
    public String findComposer() {
        return new ThirteenMonthComposer();
    }
}
複製程式碼

還需要重寫一下Song類

public abstract class Song {
    String singer;//演唱者
    String lyricist;//作詞人
    String composer;//作曲人
    abstract void prepare();//只改變了這個方法
    String Sing(){
        return "錄歌";
    }
    String Cut(){
        return "剪下";
    }
    String Mix(){
        return "混音";
    }

    @Override
    public String toString() {
        return "Song{" +
                "singer='" + singer + '\'' +
                ", lyricist='" + lyricist + '\'' +
                ", composer='" + composer + '\'' +
                '}';
    }
}
複製程式碼

現在就可以根據工廠類來寫歌曲子類了。每個廠牌都有FolkSong、RockSong、PopSong,現在不用寫那麼多子類,只需要建立一個相應子類,材料(作詞作曲演唱)就交給傳遞進去的工廠類來解決!

public class FolkSong extends Song{
    SongFactory factory;
    public FolkSong(SongFactory factory) {
        this.factory = factory;
    }
    @Override
    void prepare() {
        singer = factory.findSinger();
        lyricist = factory.findLyricist();
        composer = factory.findComposer();
    }
}
複製程式碼

現在我們幾乎完成了所有的材料,就差呼叫了。現在先來理一理這些東西。

  1. 抽象工廠類是SongFactor。
  2. 具體工廠類是MDskySongFactory 和ThirteenMonthSongFactory。
  3. 抽象產品類是Singer、Lyricist、Composer。
  4. 具體產品類是MDskySinger、ThirteenMonthSinger等。
  5. Client是MDSkyMusicLabel和ThirteenMonthMusicLabel。

現在就在Client裡面呼叫看看吧。

public class MDSkyMusicLabel extends MusicLabel {
    @Override
    Song orderSong(String type) {
        Song song = null;
        SongFactory factory = new MDskySongFactory();

       if(type.equals("folk")){
           song = new FolkSong(factory);
       }else if(type.equals("rock")){
           song = new RockSong(factory);
       }else if(type.equals("pop")){
           song = new PopSong(factory);
       }
       return song;
    }
}
複製程式碼

完美!到處都充斥著依賴倒置的清香。

體會

這個模式雖然需要些的核心類比較多,但是當需求滿足“為相互依賴的物件提供一個介面”,具體物件又比較多,又易修改的時候,這個模式的優點你就能體會到了。
優點: 閉合開閉原則,耦合低。
缺點: 不適用於物件數量少的情況。

BitmapFactory

由淺到深瞭解工廠模式
BitmapFactory是android中比較常見的工廠模式的使用。我們肯定都寫過這樣一句程式碼

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher) ;
複製程式碼

看出來什麼沒??
這特麼就是個簡單工廠模式啊!還是個靜態工廠!
為何這麼說,因為它是通過類名呼叫方法,並且返回了一個物件。這不就是簡單工廠嗎?

BitmapFactory.class
看裡面,全是紅彤彤的靜態方法。這個工廠做的事就是通過不同的引數,返回Bitmap物件。這也就是簡單工廠模式的作用。
今天的原始碼解讀就到此為止了,要問我為啥沒寫擴充套件。
你要是看懂了工廠模式,就不會問這個問題。

為什麼要用工廠模式

(此處應有彈幕:“收尾呼應,滿分作文!”)
我寫的優點裡面那麼多,還不能讓你使用工廠模式嗎?
就衝解耦合這一點,你就該用它!

總結

以下是我“設計模式系列”文章,歡迎大家關注留言投幣丟香蕉。

設計模式入門
Java與Kotlin的單例模式
Kotlin的裝飾者模式與原始碼擴充套件
由淺到深瞭解工廠模式

相關文章