工廠模式

定義

定義一個(gè)創(chuàng)建對(duì)象的接口够挂,但由子類決定要實(shí)例化的類是哪一個(gè)。工廠方法讓類把實(shí)例化推遲到子類。

使用場(chǎng)景

  • 一個(gè)類不能預(yù)期它必須創(chuàng)建的對(duì)象的類
  • 一個(gè)類想要它的子類指定它創(chuàng)建的對(duì)象
  • 類將責(zé)任委托給幾個(gè)輔助子類之一,并且你想要知道哪個(gè)輔助子類是委托

例子

有個(gè)披薩店毫胜,用戶可以下單定披薩。披薩店首先根據(jù)用戶的選擇披薩(CheesePizza昔瞧、GreekPizza)指蚁,然后按照制作流程進(jìn)行制作,最后把披薩返回給用戶:

public class PizzaStore{
    public Pizza orderPizza(String type){
        Pizza pizza;
        if(type.equals("cheese")){
            pizza = new CheesePizza();
        }else if(type.equals("greek")){
            pizza = new GreekPizza();
        }
        ...

        // 制作流程
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }
}

我們發(fā)現(xiàn)上面程序中自晰,選擇披薩是一個(gè)變化的部分凝化,而披薩的制作流程是不變的,我們把這部分抽取出來(lái)提煉一個(gè)工廠函數(shù):

// 制作工廠
public class SimplePizzaFactory{
    public Pizza createPizza(String type){
        Pizza pizza = null;
        if(type.equals("cheese")){
            pizza = new CheesePizza();
        }else if(type.equals("greek")){
            pizza = new GreekPizza();
        }
        ...
        return pizza;
    }
}

public class PizzaStore{
    SimplePizzaFactory factory;
    public PizzaStore(SimplePizzaFactory factory){
        this.factory = factory;
    }
    public Pizza orderPizza(String type){
        Pizza pizza;
        pizza = factory.createPizza(type);

        // 制作流程
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }
}

通過(guò)這個(gè)factory酬荞,我們把依據(jù)類型選擇pizza這段邏輯提取出來(lái)搓劫。這里帶來(lái)一個(gè)最簡(jiǎn)單的好處是:所有需要通過(guò)類型選擇pizza的地方都可以調(diào)用這個(gè)方法,比如結(jié)賬混巧、制作pizza的菜單等枪向。但是這還不是工廠模式。
需求進(jìn)一步擴(kuò)展咧党,pizza開(kāi)了很多的分店秘蛔,比如北京、上海傍衡,每個(gè)店就可以制作特定地區(qū)原料的pizza深员。我們可以用下面的方法實(shí)現(xiàn):

// 從北京店定制cheese披薩
BeijingPizzaFactory factory = new BeijingPizzaFactory();
PizzaStore bjStore = new PizzaStore(factory);
bjStore.orderPizza("cheese");

// 從北京店定制greek披薩
ShanghaiPizzaFactory factory = new ShanghaiPizzaFactory();
PizzaStore shStore = new PizzaStore(factory);
shStore.orderPizza("greek");

也可以通過(guò)繼承來(lái)實(shí)現(xiàn):

public abstract class PizzaStore{
    public Pizza orderPizza(String type){
        Pizza pizza;
        pizza = createPizza(type);

        ...
        return pizza;
    }
    abstract Pizza createPizza(String type);
}

// 北京披薩店
public class BeijingPizzaStore extends PizzaStore{
    public Pizza createPizza(String type){
        if(type.equals("cheese")){
            pizza = new BeijingCheesePizza();
        }
        ...
        return pizza;
    }
}

到了這一步,我們還沒(méi)有實(shí)現(xiàn)pizza類:

public abstract class Pizza{
    String name;    // 名字
    String dough;   // 面團(tuán)
    String sauce    // 醬

    void prepare(){
        // do prepare
    }

    void bake(){
        System.out.println("bake");
    }

    void cut(){
        System.out.println("cut");
    }

    void box(){
        System.out.println("box");
    }

    public String getName(){
        return name;
    }
}

public class BeijingCheesePizza extends Pizza{
    public BeijingCheesePizza(){
        name = "beijing cheese pizza";
        dough = "白面";
        sauce = "辣椒醬";
    }
}

對(duì)上面的例子做個(gè)測(cè)試:

public class App{
    public static void main(String[] args){
        BeijingPizzaStore bjStore = new BeijingPizzaStore();
        Pizza pizza = bjStore.orderPizza("cheese")
    }
}

分析

工廠模式都用來(lái)封裝對(duì)象的創(chuàng)建蛙埂。工廠方法模式通過(guò)讓子類決定該創(chuàng)建的對(duì)象是什么倦畅,來(lái)達(dá)到將對(duì)象創(chuàng)建的過(guò)程封裝的目的。
上面的例子中绣的,pizzaStore是抽象創(chuàng)建類叠赐;BeijingPizzaStore是具體創(chuàng)建類,它的createPizza方法正是工廠方法屡江,用來(lái)制作產(chǎn)品芭概;Pizza是工廠的產(chǎn)品類。
工廠模式體現(xiàn)了設(shè)計(jì)模式一個(gè)很重要的原則(依賴倒置原則):要依賴抽象惩嘉,不要依賴具體類谈山。pizzaStore依賴了Pizza這個(gè)抽象類,而不是CheesePizza宏怔、GreekPizza這些具體類奏路。
要開(kāi)一個(gè)披薩店,我們首先想到的是要能制作各種披薩臊诊,如芝士披薩鸽粉、熟食披薩等。但是如果披薩店依賴這些具體的披薩那就麻煩了(第一段程序)抓艳;反過(guò)來(lái)触机,我們把所有具體的披薩抽象成一個(gè)pizza類,那么和披薩店依賴的是這個(gè)pizza類玷或,而不是具體的披薩了儡首。

進(jìn)一步延伸

接上面披薩店的需求。不同的地方原料不一樣偏友,比如北方人面食多蔬胯,因此披薩用面來(lái)做;南方人喜歡吃米飯位他,因此披薩用大米來(lái)做(胡扯..)氛濒,因此我們提供一個(gè)接口,負(fù)責(zé)創(chuàng)建所有的原料:

public interface PizzaIngredientFactory{
    public Dough createDough();
    public Sauce createSauce();
    public Cheese createCheese();
}

那么北京的披薩店制作披薩時(shí)需實(shí)現(xiàn)這個(gè)接口鹅髓,用于準(zhǔn)備披薩的材料:

public class BeijingPizzaIngredientFactory implements PizzaIngredientFactory{
    public Dough createDough(){
        return new BaiMianDough();
    }
    public Sauce createSauce(){
        return new LajiaoSauce();
    }
}

北京的披薩店類需做修改:

public abstract class Pizza{
    String name;    // 名字
    Dough dough;    // 面團(tuán)
    Sauce sauce // 醬

    abstract void prepare(); // 由子類實(shí)現(xiàn) 準(zhǔn)備原料

    ...
}

// 具體的披薩 和 原料關(guān)聯(lián)舞竿,原料的實(shí)現(xiàn)由工廠方法提供
public class CheesePizza extends Pizza{
    PizzaIngredientFactory ingredientFactory;

    public CheesePizza(PizzaIngredientFactory ingredientFactory){
        this.ingredientFactory = ingredientFactory;
    }

    void prepare(){
        dough = ingredientFactory.createDough();
        sauce = ingredientFactory.createSauce();
        cheese = ingredientFactory.createCheese();
    }
}

public class BeijingPizzaStore extends PizzaStore{
    protected Pizza createPizza(String item){
        Pizza pizza = null;
        PizzaIngredientFactory ingredientFactory = new BeijingPizzaIngredientFactory();
        if(item.equals("cheese")){
            pizza = new CheesePizza(ingredientFactory);
        }
        ...

        return pizza;
    }
}

抽象工廠類

抽象工廠類模式提供一個(gè)接口,用于創(chuàng)建相關(guān)或依賴對(duì)象的家族窿冯,而不需要明確指定具體類骗奖。由于不需要知道實(shí)際產(chǎn)出的具體產(chǎn)品(上面例子中BaiMianDough、LajiaoSauce)是什么醒串,這樣客戶就從具體的產(chǎn)品中解耦执桌。

兩者區(qū)別

工廠方法: 用來(lái)創(chuàng)建對(duì)象,具體說(shuō)是通過(guò)子類創(chuàng)建對(duì)象厦凤”撬保客戶只需要知道他們所使用的抽象類型即可,而由子類負(fù)責(zé)決定具體類型较鼓。負(fù)責(zé)將客戶從具體類型中解耦椎木。
抽象工廠方法:創(chuàng)建一個(gè)產(chǎn)品家族的抽象類型,這個(gè)類型的子類定義了產(chǎn)品被產(chǎn)生的方法博烂。

簡(jiǎn)單點(diǎn)的例子

參考游戲《鐵匠》:

// 鐵匠能夠制作武器
public interface Blacksmith {
  Weapon manufactureWeapon(WeaponType weaponType);
}

// elf制作精靈族的武器
public class ElfBlacksmith implements Blacksmith {
  public Weapon manufactureWeapon(WeaponType weaponType) {
    return new ElfWeapon(weaponType);
  }
}

// orc制作獸族的武器
public class OrcBlacksmith implements Blacksmith {
  public Weapon manufactureWeapon(WeaponType weaponType) {
    return new OrcWeapon(weaponType);
  }
}

// 武器與種族和類別有關(guān)
public interface Weapon {
  WeaponType getWeaponType();
}

// 精靈族的武器
public class ElfWeapon implements Weapon {
  private WeaponType weaponType;

  public ElfWeapon(WeaponType weaponType) {
    this.weaponType = weaponType;
  }

  public String toString() {
    return "Elven " + weaponType;
  }

  public WeaponType getWeaponType() {
    return weaponType;
  }
}

// 獸族的武器
public class OrcWeapon implements Weapon {
  private WeaponType weaponType;

  public OrcWeapon(WeaponType weaponType) {
    this.weaponType = weaponType;
  }

  public String toString() {
    return "Orcish " + weaponType;
  }

  public WeaponType getWeaponType() {
    return weaponType;
  }
}


// 武器本身分為 短劍香椎、矛、斧三種
public enum WeaponType {
  SHORT_SWORD("short sword"), SPEAR("spear"), AXE("axe"), UNDEFINED("");
  private String title;

  WeaponType(String title) {
    this.title = title;
  }
  public String toString() {
    return title;
  }
}

// 測(cè)試  調(diào)用的是鐵匠這個(gè)基類禽篱,通過(guò)指向不同的對(duì)象畜伐,定義其行為
public class App {

  private final Blacksmith blacksmith;
  
  public App(Blacksmith blacksmith) {
    this.blacksmith = blacksmith;
  }
  
  public static void main(String[] args) {
    // 用獸族的武器去戰(zhàn)爭(zhēng)
    App app = new App(new OrcBlacksmith());
    app.manufactureWeapons();
    
    // 用精靈族的武器去戰(zhàn)爭(zhēng)
    app = new App(new ElfBlacksmith());
    app.manufactureWeapons();
  }
  
  private void manufactureWeapons() {
    Weapon weapon;
    weapon = blacksmith.manufactureWeapon(WeaponType.SPEAR); // 矛
    System.out.println(weapon);
    weapon = blacksmith.manufactureWeapon(WeaponType.AXE); // 斧
    System.out.println(weapon);
  }
}

參考

iluwatar/java-design-patterns

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市躺率,隨后出現(xiàn)的幾起案子玛界,更是在濱河造成了極大的恐慌万矾,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件慎框,死亡現(xiàn)場(chǎng)離奇詭異良狈,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)笨枯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)薪丁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人馅精,你說(shuō)我怎么就攤上這事严嗜。” “怎么了洲敢?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵漫玄,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我沦疾,道長(zhǎng)称近,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任哮塞,我火速辦了婚禮刨秆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘忆畅。我一直安慰自己衡未,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布家凯。 她就那樣靜靜地躺著缓醋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪绊诲。 梳的紋絲不亂的頭發(fā)上送粱,一...
    開(kāi)封第一講書(shū)人閱讀 49,764評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音掂之,去河邊找鬼抗俄。 笑死,一個(gè)胖子當(dāng)著我的面吹牛世舰,可吹牛的內(nèi)容都是我干的动雹。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼跟压,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼胰蝠!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤茸塞,失蹤者是張志新(化名)和其女友劉穎躲庄,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體翔横,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡读跷,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了禾唁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡无切,死狀恐怖荡短,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情哆键,我是刑警寧澤掘托,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站籍嘹,受9級(jí)特大地震影響闪盔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜辱士,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一泪掀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧颂碘,春花似錦异赫、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至峡竣,卻和暖如春靠抑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背适掰。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工颂碧, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人攻谁。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓稚伍,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親戚宦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子个曙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容