大話設計模式學習筆記(15)——抽象工廠模式

源碼git地址 https://github.com/dlovetco/designMode
前面我們學過簡單工廠,工廠方法兩種“工廠”有關的設計模式。今天讓我們來看看這個抽象工廠是怎麼回事。

問題提出

小明和小紅是夫妻,他們決定在裝修的時候提出兩種室內設計。因爲誰也不能說服誰,所以這兩種設計都要保留,不到最後一刻都說不準採用哪一種方法。此外需要小紅和小明都給出一個裝修模型。要求用代碼實現上述場景,並且做到修改最少的代碼就可以切換設計方法。

(設裝修房子分爲裝修臥室,裝修廚房兩部分)

抽象工廠

package abstractfactory;

public class AbstractFactory {

    public static void main(String[] args) {
        //先使用小明的方法
        Kitchen kitchen = new MingDecorate().decorateKitchen();
        Bedroom bedroom = new MingDecorate().decorateBedroom();
        System.out.println(kitchen);
        System.out.println(bedroom);

        //再使用小紅的方法
        kitchen = new HongDecorate().decorateKitchen();
        bedroom = new HongDecorate().decorateBedroom();
        System.out.println(kitchen);
        System.out.println(bedroom);
    }
}

/**
 * 裝飾方法
 */
interface DecorateFactory {

    Bedroom decorateBedroom();

    Kitchen decorateKitchen();
}

/**
 * 未被裝修的廚房
 */
interface Kitchen {

}

class MingKitchen implements Kitchen {
    @Override
    public String toString() {
        return "小明裝修出來的廚房";
    }
}

class HongKitchen implements Kitchen {
    @Override
    public String toString() {
        return "小紅裝修出來的廚房";
    }
}

interface Bedroom {

}

class MingBedroom implements Bedroom {
    @Override
    public String toString() {
        return "小明裝修出來的臥室";
    }
}

class HongBedroom implements Bedroom {
    @Override
    public String toString() {
        return "小紅裝修出來的臥室";
    }
}


class MingDecorate implements DecorateFactory {

    @Override
    public Bedroom decorateBedroom() {
        return new MingBedroom();
    }

    @Override
    public Kitchen decorateKitchen() {
        return new MingKitchen();
    }
}

class HongDecorate implements DecorateFactory {

    @Override
    public Bedroom decorateBedroom() {
        return new HongBedroom();
    }

    @Override
    public Kitchen decorateKitchen() {
        return new HongKitchen();
    }
}

這其實就是抽象工廠模式了。我們可以看到切換兩種裝修模式的代價 僅僅是修改工廠的實例類型(即MingDecorate還是HongDecorate)。這是依賴倒轉好處的體現。

我們再簡單分析一下上述代碼。
1. 首先看題目,我們發現雖然有兩種不同的裝修方式,但都需要裝修臥室和廚房。所以我們可以把這兩種方法抽象出來作爲一個接口(DecorateFactroy),這樣的話通過依賴倒轉,我們切換模式只需要修改接口的實例。
2. 有了裝修方法,我們就可以通過裝修方法來產生每種方法自定義的實例。這裏比如:MingKitchen,HongKitchen。於是我們根據依賴倒轉,也可以抽象出一個接口Kitchen。Washroom跟Kichen,此處不再贅述。

plantuml:

@startuml
interface DecorateFactory{
{abstract}Bedroom decorateBedroom()
{abstract}Kitchen decorateKitchen()
}
DecorateFactory <|.. MingDecorate
class MingDecorate{
Bedroom decorateBedroom()
Kitchen decorateKitchen()
}
DecorateFactory <|.. HongDecorate
class HongDecorate{
Bedroom decorateBedroom()
Kitchen decorateKitchen()
}
Kitchen <.. DecorateFactory
interface Kitchen{
}
Kitchen <|.. MingKitchen
class MingKitchen{
}
Kitchen <|.. HongKitchen
class HongKitchen{
}
Bedroom <.. DecorateFactory
interface Bedroom{
}
Bedroom <|.. MingBedroom
class MingBedroom{
}
Bedroom <|.. HongBedroom
class HongBedroom{
}
@enduml

這裏寫圖片描述

利用簡單工廠來優化抽象工廠

我們看抽象工廠其實是很像工廠方法的。區別在於工廠方法用於製造不同的對象。而抽象工廠用於製造同一個對象,而在製造過程中有不同的做法(即多種不同的實現方法)。

抽象工廠的缺點

  1. 與客戶端的耦合過重,客戶端中有許多具體的類。
  2. 雖然說已經在切換方法上我們所要付出的代價足夠小,但是我們還是需要在實例化工廠的時候修改。如果在多個地方實例化了工廠對象,則我們還是要修改多個地方。
    下面用簡單工廠來優化
package abstractfactory.simplefactory;

public class SimpleFactory {

    public static void main(String[] args) {
        DecorateFactory decorateFactory = new DecorateFactory();
        Kitchen kitchen = decorateFactory.createKitchen();
        Bedroom bedroom = decorateFactory.createBedroom();
        System.out.println(kitchen);
        System.out.println(bedroom);
    }
}

/**
 * 未被裝修的廚房
 */
interface Kitchen {

}

class MingKitchen implements Kitchen {
    @Override
    public String toString() {
        return "小明裝修出來的廚房";
    }
}

class HongKitchen implements Kitchen {
    @Override
    public String toString() {
        return "小紅裝修出來的廚房";
    }
}

interface Bedroom {

}

class MingBedroom implements Bedroom {
    @Override
    public String toString() {
        return "小明裝修出來的臥室";
    }
}

class HongBedroom implements Bedroom {
    @Override
    public String toString() {
        return "小紅裝修出來的臥室";
    }
}

class DecorateFactory {
    private static String type = "小明";

    Kitchen createKitchen() {
        switch (type) {
            case "小明":
                return new MingKitchen();
            case "小紅":
                return new HongKitchen();
            default:
                return null;
        }
    }

    Bedroom createBedroom() {
        switch (type) {
            case "小明":
                return new MingBedroom();
            case "小紅":
                return new HongBedroom();
            default:
                return null;
        }
    }
}

這樣的話如果要修改只需要,在DecorateFactory中修改String字段爲“小紅”。付出的代價比原版抽象工廠要小,且與客戶端的耦合性大大減少。因爲實例的switch判斷移交給了簡單工廠。

使用反射再次優化

反射是什麼,在這裏就不詳細解釋了。在java中,反射可以通過類名來實例化特定的類。所以我們可以通過傳入類名參數,來拋棄掉switch。
這裏寫圖片描述
我的目錄結構如上圖所示。

package abstractfactory.reflect;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Properties;

public class Reflect {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {
        DecorateFactory decorateFactory = new DecorateFactory();
        Kitchen kitchen = decorateFactory.createKitchen();
        Bedroom bedroom = decorateFactory.createBedroom();
        System.out.println(kitchen);
        System.out.println(bedroom);
    }
}

/**
 * 未被裝修的廚房
 */
interface Kitchen {

}

class MingKitchen implements Kitchen {
    @Override
    public String toString() {
        return "小明裝修出來的廚房";
    }
}

class HongKitchen implements Kitchen {
    @Override
    public String toString() {
        return "小紅裝修出來的廚房";
    }
}

interface Bedroom {

}

class MingBedroom implements Bedroom {
    @Override
    public String toString() {
        return "小明裝修出來的臥室";
    }
}

class HongBedroom implements Bedroom {
    @Override
    public String toString() {
        return "小紅裝修出來的臥室";
    }
}

class DecorateFactory {
    private static String type = "小明";
    private Properties properties = new Properties();

    Kitchen createKitchen() throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
        properties.load(new InputStreamReader(new FileInputStream("./src/abstractfactory/reflect/test.properties"), "utf-8"));
        String className = properties.getProperty("kitchenMean");
        return (Kitchen) Class.forName("abstractfactory.reflect." + className).newInstance();
    }

    Bedroom createBedroom() throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
        properties.load(new InputStreamReader(new FileInputStream("./src/abstractfactory/reflect/test.properties"), "utf-8"));
        String className = properties.getProperty("bedRoomMean");
        return (Bedroom) Class.forName("abstractfactory.reflect." + className).newInstance();
    }
}

test.properties

# 臥室裝修方法
bedRoomMean=HongBedroom
# 廚房裝修方法
kitchenMean = HongKitchen

上述代碼使用反射讀取properties裏面的參數。這樣在切換的時候只要修改properties裏面的參數就可以了。代碼也不需要重新編譯

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