天天看點

設計模式(1)之工廠模式

設計模式(1)之工廠模式

以下為我個人學習工廠模式的拙見,如有重複,純屬巧合。如有錯誤不當之處,請在留言區不吝賜教。

工廠模式細分之下有三種:簡單工廠模式、工廠方法以及抽象工廠模式。

先上枯燥的概念:

  • 工廠方法模式:定義了一個建立對象的接口,但由子類決定要執行個體化的類是哪一個。工廠方法讓類把執行個體化推遲到子類。
  • 抽象工廠模式:提供一個接口,用于建立相關或依賴對象的家族,而不需要明确指定具體類。

光看概念,很迷糊。那是因為概念還是比較抽象的。首先撇開概念不看,我們得知道java中工廠都用來幹什麼的?答案很簡單,建立對象的。那為什麼還要細分這麼個三類呢?我們找一個現實生活中的例子來舉例吧。

比如我們需要衣服,在原始社會那都是我們老祖宗自己用樹葉和藤條做出來的。類比于java裡對象的new。但是不是每個老祖宗都會縫制啊,怎麼辦,那就專門由一個人來縫制衣服。

随着時代進步衣服的分類開始變得豐富起來,有薄的厚的,有短袖長袖,有褲子裙子等等。這下子一個人就忙不過來了,那就建立一個工廠專門做衣服吧,這個工廠由若幹人組成,有人做上衣有人做褲子等等。使用者需要什麼類型的衣服,直接跟工廠說,工廠直接傳回使用者要的。這個類比工廠方法,用來生産某一類産品。

再到後來,用料上也很開始複雜起來。有純羊毛的、滌綸的、蕾絲的還有冰蠶絲的等等。做衣服的工廠還是負責做衣服,但是原材料也需要工廠來做呀。而且需要什麼樣的布料,染成什麼顔色,這個需要做衣服的工廠來知道。這就類似于抽象工廠模式了。

說多了容易亂,上代碼!

一、建立對象(工廠的核心作用)

最原始的生産衣服的方法,就是自己動手,豐衣足食:

public class Clothes {
    public String toString(){
        return "我是一件衣服";
    }
}
           

然後生産這件衣服:

public class ClothesTest {
    public static void main(String[] args) {
        Clothes clothes = new Clothes();
        System.out.println(clothes);
    }
}
           

運作得到的結果:

我是一件衣服

别誤會,這可不是設計模式,隻是先示範一下對象的建立,畢竟工廠最原始的地方也是這麼操作的。

當然java中建立對象的方法不止這一種,這裡重點是放在工廠模式上,就不深入其他方法了。

二、簡單工廠

時代進步了,衣服種類也多了,建立一個小工廠吧。我們可以将衣服類設定為抽象類或接口,為了要統一衣服的标準,這裡采用抽象類的方式,當然接口也是ok的:

public abstract class Clothes {
    // 必須告訴大家,你是啥類型的衣服
    public abstract String toString();
}
           

然後新增褲子和襯衫的類型:

public class Trousers extends Clothes {
    @Override
    public String toString() {
        return "我是一條褲子";
    }
}
           
public class Shirt extends Clothes {
    @Override
    public String toString() {
        return "我是一件襯衫";
    }
}
           
public class FeatureClothes extends Clothes {
    @Override
    public String toString() {
        return "未來創新,暫時無法制作";
    }
}
           

建立一個簡單的小工廠來根據使用者需要生産衣服:

public class SimpleClothesFactory {
    /**
     * 根據使用者需要的類型,生産對應的衣服
     * @param clazz
     * @return
     */
    public static Clothes createClothes(Class<? extends Clothes> clazz){
        try {
            return clazz.getDeclaredConstructor().newInstance();
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}
           

使用者的需求來啦,生産衣服啦:

public class SimpleClothesFactoryTest {
    public static void main(String[] args) {
        // 生産衣服的工廠
        SimpleClothesFactory clothesFactory = new SimpleClothesFactory();
        // 生産一條褲子
        Clothes trousers = clothesFactory.createClothes(Trousers.class);
        System.out.println(trousers);
        // 生産一件襯衫
        Clothes shirt = clothesFactory.createClothes(Shirt.class);
        System.out.println(shirt);
    }
}
           

輸出的結果:

我是一條褲子

我是一件襯衫

以上就是簡單工廠模式實作的一種方式,如果需要新增衣服種類,隻需要到SimpleClothesFactory裡添加就好了,至于使用者,他隻需要跟工廠要就好了。

三、工廠方法模式

現在衣服分類越來越多了,褲子有牛仔褲(jeans)、短裙(skirt)等,而襯衫有緊身女衫(blouse)、運動衫(sweater)等。

老規矩,先上衣服:

public abstract class Clothes {
    // 必須告訴大家,你是啥類型的衣服
    public abstract String toString();
}
           
public class Jeans extends Clothes {
    @Override
    public String toString() {
        return "我是一條牛仔褲";
    }
}
           
public class Skirt extends Clothes {
    @Override
    public String toString() {
        return "我是一條裙子";
    }
}
           
public class Blouse extends Clothes {
    @Override
    public String toString() {
        return "我是一件緊身女衫";
    }
}
           
public class Sweater extends Clothes {
    @Override
    public String toString() {
        return "我是一件運動衫";
    }
}
           

然後建立一個統一的工廠,這裡我們使用接口,必須實作createClothes方法:

public interface ClothesFactory {
    // 所有工廠都得實作該方法,根據類型制作需要的衣服
    Clothes createClothes(String type);
}
           

然後就是分别生産上衣和褲子的工廠了:

public class ShirtFactory implements ClothesFactory {
    @Override
    public Clothes createClothes(String type) {
        Clothes clothes = null;
        if(type.equalsIgnoreCase("blouse")){
            clothes = new Blouse();
        }else if(type.equalsIgnoreCase("sweater")){
            clothes = new Sweater();
        }else{
            clothes = new FeatureClothes();
        }
        return clothes;
    }
}
           
public class TrousersFactory implements ClothesFactory {
    @Override
    public Clothes createClothes(String type) {
        Clothes clothes = null;
        if(type.equalsIgnoreCase("jeans")){
            clothes = new Jeans();
        }else if(type.equalsIgnoreCase("skirt")){
            clothes = new Skirt();
        }else{
            clothes = new FeatureClothes();
        }
        return clothes;
    }
}
           

最後使用者隻需要根據自己的需要,找對應的工廠要産品就好了:

public class FactoryMethodTest {
    public static void main(String[] args) {
        ClothesFactory factory = new TrousersFactory();
        Clothes jeans = factory.createClothes("jeans");
        System.out.println(jeans);

        factory = new ShirtFactory();
        Clothes blouse = factory.createClothes("blouse");
        System.out.println(blouse);
    }
}
           

輸出結果:

我是一條牛仔褲 我是一件緊身女衫

以後生産帽子,就加個帽子工廠,生産連衣裙就來個連衣裙工廠,友善了分類。

可以看出,工廠方法模式比起簡單工廠模式的好處就是将許多雜亂的對象進行了分類。每個工廠都負責生産一類産品,無論是單個工廠新增一個産品還是新增一個工廠生産一套極具創意的産品都非常友善。

說完了好處,那就來說說壞處吧,雖然簡單的分了類,但是每個工廠依然我行我素,雜亂無章。沒有好的統一标準,萬一有幾個不良工廠用次品布料,産出來的衣服容易讓人皮膚過敏那就麻煩了。況且生産不同衣服的原料也各不相同,那操作起來那是相當麻煩。此時就輪到我們最後的壓軸,抽象工廠模式登場了。

四、抽象工廠模式

首先我們需要定義幾個原料及其具體分類,布料用的是棉布的還是絲綢的。用的線是尼龍的還是棉的。

這裡先約定下,棉布棉線生産一類衣服,絲綢尼龍生産一類衣服。您也甭管這啥奇葩的搭配了,咱就先這麼約定着。

public interface Cloth {
    String toString();
}
           
public class Cotton implements Cloth{
    @Override
    public String toString(){
        return "棉布";
    }
}
           
public class Silk implements Cloth{
    @Override
    public String toString(){
        return "絲綢";
    }
}
           
public interface Thread {
    String toString();
}
           
public class CottonThread implements Thread {
    @Override
    public String toString(){
        return "棉線";
    }
}
           
public class NylonThread implements Thread {
    @Override
    public String toString(){
        return "尼龍線";
    }
}
           

建立一個原料工廠,規定了接口,必須生産原料:

public interface IngredientFactory {
    Thread createThread();
    Cloth createCloth();
}
           

絲綢類原料工廠:

public class SilkIngredientFactory implements IngredientFactory{

    @Override
    public Thread createThread() {
        return new NylonThread();
    }

    @Override
    public Cloth createCloth() {
        return new Silk();
    }
}
           

棉類原料工廠:

public class CottonIngredientFactory implements IngredientFactory {

    @Override
    public Thread createThread() {
        return new CottonThread();
    }

    @Override
    public Cloth createCloth() {
        return new Cotton();
    }
}
           

對比兩個原料工廠差別,兩者使用的是不同的原材料。

好了,我們來實作一下衣服吧,需要改進了:

public abstract class Clothes {
    public abstract String toString();
}
           
public class Shirt extends Clothes {

    IngredientFactory ingredientFactory;

    public Shirt(IngredientFactory ingredientFactory){
        this.ingredientFactory = ingredientFactory;
    }

    @Override
    public String toString() {
        Cloth cloth = ingredientFactory.createCloth();
        Thread thread = ingredientFactory.createThread();
        return cloth.toString() + thread.toString() + "襯衫";
    }
}
           
public class Trousers extends Clothes {

    IngredientFactory ingredientFactory;

    public Trousers(IngredientFactory ingredientFactory){
        this.ingredientFactory = ingredientFactory;
    }

    @Override
    public String toString() {
        Cloth cloth = ingredientFactory.createCloth();
        Thread thread = ingredientFactory.createThread();
        return cloth.toString() + thread.toString() + "褲子";
    }
}
           

現在定義一個商店吧:

public abstract class ClothesStore {
    public abstract Clothes createClothes(String type);
}
           

再細分下褲子商店和衣服商店:

public class TrousersStore extends ClothesStore {
    @Override
    public Clothes createClothes(String type) {
        Clothes clothes = null;
        IngredientFactory ingredientFactory = null;
        if(type.equalsIgnoreCase("silk")){
            ingredientFactory = new SilkIngredientFactory();
            clothes = new Trousers(ingredientFactory);
        }else if(type.equalsIgnoreCase("cotton")){
            ingredientFactory = new CottonIngredientFactory();
            clothes = new Trousers(ingredientFactory);
        }else {
            clothes = null;
        }
        return clothes;
    }
}
           
public class ShirtStore extends ClothesStore {
    @Override
    public Clothes createClothes(String type) {
        Clothes clothes = null;
        IngredientFactory ingredientFactory = null;
        if(type.equalsIgnoreCase("silk")){
            ingredientFactory = new SilkIngredientFactory();
            clothes = new Shirt(ingredientFactory);
        }else if(type.equalsIgnoreCase("cotton")){
            ingredientFactory = new CottonIngredientFactory();
            clothes = new Shirt(ingredientFactory);
        }else {
            clothes = null;
        }
        return clothes;
    }
}
           

最後我們來測試一下:

public class AbstractFactoryTest {
    public static void main(String[] args) {
        ClothesStore store = new ShirtStore();
        Clothes clothes = store.createClothes("silk");
        System.out.println(clothes);

        store = new TrousersStore();
        clothes = store.createClothes("cotton");
        System.out.println(clothes);

        clothes = store.createClothes("silk");
        System.out.println(clothes);
    }
}
           

輸出結果:

絲綢尼龍線襯衫 棉布棉線褲子 絲綢尼龍線褲子

可以看到你想買啥衣服,你就找哪個商店,需要什麼材質的,隻需要跟這個商店說就可以得到想要材質的衣服啦。

關于抽象工廠我們來分析一下:細心的你應該會發現,抽象工廠模式的實作過程中用到了工廠方法模式,這裡總的ClothesStore可以當成一個工廠,然後細分了上衣店和褲子店。用的是工廠方法模式,而生産用到的原料工廠,就是抽象工廠模式啦。通過抽象工廠模式,一步一步的組合原料,最後由商店根據使用者的選擇去找對應的原料工廠就好了。

後續擴充也是非常友善,新增一個種類的原料,比如紐扣,那就加個紐扣的接口,在原料工廠裡增加一個api,create紐扣,這樣在生産具體衣服的時候直接調用api就好了。是否覺得擴充起來十分友善呢。

最後總結一下工廠模式用到的設計模式原則:

依賴抽象,不要依賴具體類

可以看到我們都是使用抽象類作為變量類型,而用其子類作為具體實作,這樣每一層直接都得到解耦,不會過分依賴,修改起來也十分友善。

這裡需要注意的是,工廠模式雖然統一了對象的建立,但是在實際業務場景中,對象的建立是十分複雜的。就比如使用工廠模式最典型的spring建立bean的時候,看過源碼的應該都知道,這一塊的源碼是相當複雜的。而且這裡還違背了設計原則中的開閉原則。是以工廠模式的使用需要結合實際業務場景來決定。

學習設計模式,切記并不是編碼的時候用的越多越好,而是得用的巧,靈活運用設計模式才能提高代碼的品質。

繼續閱讀