源碼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
利用簡單工廠來優化抽象工廠
我們看抽象工廠其實是很像工廠方法的。區別在於工廠方法用於製造不同的對象。而抽象工廠用於製造同一個對象,而在製造過程中有不同的做法(即多種不同的實現方法)。
抽象工廠的缺點
- 與客戶端的耦合過重,客戶端中有許多具體的類。
- 雖然說已經在切換方法上我們所要付出的代價足夠小,但是我們還是需要在實例化工廠的時候修改。如果在多個地方實例化了工廠對象,則我們還是要修改多個地方。
下面用簡單工廠來優化
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裏面的參數就可以了。代碼也不需要重新編譯。