Java設計模式之創建型模式:工廠模式

一、工廠模式簡介

工廠模式是用來封裝對象的創建的,通過將創建對象的代碼提取出來,減少應用程序和具體類之間的依賴來達到鬆耦合的目的。這幫助了我們針對接口編程,而不是針對具體類編程。

下面通過模擬餐館訂餐來學習工廠模式,有以下需求:

  1. 菜有很多種,比如PekingDuck,DriedChicken等等
  2. 菜的製作分爲以下幾個步驟:prepare、make、box。
  3. 應用不同的工廠模式:簡單工廠模式、工廠方法模式、抽象工廠模式來模擬菜的製作過程。

下面首先使用簡單工廠模式來完成上面的需求

二、簡單工廠模式

1.簡介

簡單工廠模式其實不是一個設計模式,反而比較像是一種編程習慣。簡單工廠模式就是將創建對象的代碼移到工廠對象中,由工廠對象專職負責創建其他的產品對象。

2.實際應用

根據上述需求和簡單工廠模式,設計UML類圖,如下:

在這裏插入圖片描述
這裏的FoodOrder類作爲工廠的“客戶”,該類通過SimpleFactory取得食物的實例。SimpleFactory類作爲創建食物的“工廠”,Food類作爲這個工廠的“產品”。而FriedChicken類和PekingDuck類就是“具體產品”了。

具體實現代碼:
把Food定義爲抽象類,僅實現box(打包)方法,其子類需要實現prepare()方法和make()方法

/*
 * 抽象的食品基類
 */
public abstract class Food {
	private String name;

	// 準備食品的原材料,每個食品原材料不同,由其子類實現,下同
	public abstract void prepare();

	// 食品製作
	public abstract void make();

	// 打包食品
	public void box() {
		System.out.println(name + "打包完成");
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

而繼承Food類的FriedChicken類和PekingDuck類,就需要具體實現抽象的prepare()和make()方法。

SimpleFactory類封裝所有創建對象的代碼,其中createFood()接受參數決定返回具體的食品對象。

public class SimpleFactory {
	// 根據類型創建不同的食品,返回食品對象
	public Food createFood(String foodName) {
		Food food = null;
		System.out.println("--簡單工廠模式--");
		if (foodName.equals("FriedChicken")) {
			food = new FriedChicken();
			food.setName("炸雞");
		} else if (foodName.equals("PekingDuck")) {
			food = new PekingDuck();
			food.setName("北京烤鴨");
		}
		return food;
	}
}

FoodOrder類作爲工廠的客戶,模擬用戶訂購食物。getFoodName()方法用於獲取請求,acceptOrder()處理請求。

public class FoodOrder {
	private SimpleFactory simpleFactory;// 簡單工廠對象,SimpleFactory的引用

	// 獲取請求,製作食品
	public void acceptOrder(SimpleFactory simpleFactory) {
		Food food;// 食品對象
		String foodName = "";// 用戶輸入的類型
		this.simpleFactory = simpleFactory;
		do {
			foodName = getFoodName();
			food = simpleFactory.createFood(foodName);
			if (food != null) {
				food.prepare();
				food.make();
				food.box();
			} else {
				System.out.println("輸入的食品無供應");
			}
		} while (true);
	}
	private String getFoodName() {
		BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
		String foodName = null;
		System.out.println("輸入要訂購的食品——》:");
		try {
			foodName = bufferedReader.readLine();
		} catch (IOException e) {
			System.out.println("輸入有誤!");
			e.printStackTrace();
		}
		return foodName;
	}
}

3.小結

  • 簡單工廠模式是由一個工廠對象決定創建出哪一種產品類的實例。
  • 簡單工廠模式通過封裝實例化對象的行爲,使得這個工廠爲多個客戶提供服務。像上面的項目,就可以還有HomeDelivery(宅急送)類作爲客戶使用到這個工廠。
  • 這樣設計符合OCP(開閉原則),當需要增加產品對象時,只需改變工廠的類,而不需要改變作爲客戶的類。

三、工廠方法模式

此時餐館的生意做大了,分別提供韓國料理和中國美食。假設需要兩個區域來分開兩種食品的製作。但是他們需要共用處理訂單的方法,這裏使用簡單工廠模式,創建不同的簡單工廠類也是可以的。但是如果要考慮到項目規模,軟件的可維護性、可拓展性,使用工廠方法模式是更好的選擇。

1.簡介

工廠方法模式定義了一個創建對象的抽象方法,由子類決定要實例化的類。工廠方法模式將實例化推遲到子類。

下面是描述工廠方法模式的原理UML類圖:

在這裏插入圖片描述

2.具體應用

使用工廠方法模式重新設計UML類圖:
在這裏插入圖片描述
可以看到通過工廠方法模式,細化了每一類的職責(一個類負責一種菜系的菜對象創建),當我們需要有新的菜系加入時,就只需繼承FoodOrder類,然後該類專門負責創建該菜系的對象即可。

下面根據類圖修改程序:
首先聲明一個工廠方法,FoodOrder對應Creator(抽象構造者類),這裏的工廠方法對應FoodOrder抽象類中的createFood()方法。

public abstract class FoodOrder {
	/*
	 * 獲取請求,製作食品
	 */
	public void acceptOrder() {
		// 食品對象
		Food food;
		String foodName = "";// 用戶輸入的類型
		do {
			foodName = getFoodName();
			food = createFood(foodName);
			if (food != null) {
				food.prepare();
				food.make();
				food.box();
			} else {
				System.out.println("輸入的食品無供應");
			}
		} while (true);
	}
	/*
	 * 實例化對象的責任移到這個方法中,此方法負責處理對象的創建,並把這樣的行爲封裝到子類中。
	 */
	public abstract Food createFood(String foodName);

	private String getFoodName() {
		BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
		String foodName = null;
		System.out.println("輸入要訂購的食品——》:");
		try {
			foodName = bufferedReader.readLine();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			System.out.println("輸入有誤!");
			e.printStackTrace();
		}
		return foodName;
	}
}

因爲工廠方法是抽象的,所以依賴子類來處理對象。

然後創建兩個ConcreteCreator(具體構建者):ChineseFoodOrder類和KrFoodOrder類,並實現createFood方法創建自己負責的食物。

/*
 * 處理中國食物的訂購
 * 工廠方法模式——createFood()方法負責創建中國食物的對象
 */
public class ChineseFoodOrder extends FoodOrder {
	@Override
	public Food createFood(String foodName) {
		// TODO Auto-generated method stub
		Food food = null;
		System.out.println("--工廠方法模式--");
		if (foodName.equals("HotDryNoodles")) {
			food = new HotDryNoodles();
			food.setName("武漢熱乾麪");
		} else if (foodName.equals("PekingDuck")) {
			food = new PekingDuck();
			food.setName("北京烤鴨");
		}
		return food;
	}
}
/*
 * 處理外國食物的訂購
 * 工廠方法模式——createFood()方法負責創建韓國食物的對象
 */
public class KrFoodOrder extends FoodOrder {
	@Override
	public Food createFood(String foodName) {
		// TODO Auto-generated method stub
		Food food = null;
		System.out.println("--工廠方法模式--");
		if (foodName.equals("FriedChicken")) {
			food = new FriedChicken();
			food.setName("韓式炸雞");
		} else if (foodName.equals("KimChi")) {
			food = new KimChi();
			food.setName("韓國泡菜");
		}
		return food;
	}
}

3.小結

  • 工廠方法使用繼承,把對象的創建委託給子類,子類實現工廠方法來創建對象。
  • 工廠方法允許類將實例化延遲到子類進行。當然我們也可以定義一個默認的工廠方法用於創建對象。
  • 簡單工廠和工廠方法的差異:簡單工廠將全部事情在一個地方處理了;而工廠方法是創建一個框架,讓子類決定如何實現。

四、抽象工廠模式

1.簡介

抽象工廠模式提供一個接口用於創建相關或依賴對象的家族,而不需要明確指定具體類。

下面通過一個UML類圖來描述抽象工廠模式的原理:
在這裏插入圖片描述
抽象工廠允許客戶使用抽象的接口來創建一組相關產品,而不需要知道(或關心)實際產出的具體產品是什麼,從而實現客戶與具體的產品中解耦。

抽象工廠模式體現的是將一類產品的創建集中在一個工廠。而工廠方法模式和簡單工廠模式的工廠是創建一種產品

2.具體應用

下面有個新的需求,根據客戶的口味:辣的和普通的,生成不同口味的菜,這裏仍然將不同菜系作爲產品類的劃分,(把口味作爲不同的產品類劃分,分爲不同口味的工廠更能體現抽象工廠的設計)

根據抽象工廠方法模式重新設計程序的類圖,如下:
在這裏插入圖片描述

首先定義抽象工廠的抽象層

//抽象工廠模式的抽象層(接口)
public interface AbsFactory {
	public Food createFood(String foodName, String flavor);// 根據flavor口味調用不同的方法創建對象

	public Food createSpicyFood(String foodName);// 創建辣的食物

	public Food createCommonFood(String foodName);// 創建普通的食物
}

兩個工廠子類:CHNFactory和KrFactory,實現上面的接口,分別負責創建中國菜和韓國菜。
中國菜創建工廠


	@Override
	public Food createFood(String foodName, String flavor) {
		System.out.println("--抽象工廠模式--");
		Food food = null;
		if ("spicy".equals(flavor)) {
			food = createSpicyFood(foodName);
		} else if ("common".equals(flavor)) {
			food = createCommonFood(foodName);
		}
		return food;
	}

	@Override
	public Food createSpicyFood(String foodName) {
		Food food = null;
		if (foodName.equals("HotDryNoodles")) {
			food = new HotDryNoodles();
			food.setName("辣的武漢熱乾麪");
		} else if (foodName.equals("PekingDuck")) {
			food = new PekingDuck();
			food.setName("辣的北京烤鴨");
		}
		return food;
	}

	@Override
	public Food createCommonFood(String foodName) {
		Food food = null;
		if (foodName.equals("HotDryNoodles")) {
			food = new HotDryNoodles();
			food.setName("原味的武漢熱乾麪");
		} else if (foodName.equals("PekingDuck")) {
			food = new PekingDuck();
			food.setName("原味的北京烤鴨");
		}
		return food;
	}

韓國菜創建工廠

public class KrFactory implements AbsFactory {

	@Override
	public Food createFood(String foodName, String flavor) {
		System.out.println("--抽象工廠模式--");
		Food food = null;
		if ("spicy".equals(flavor)) {
			food = createSpicyFood(foodName);
		} else if ("common".equals(flavor)) {
			food = createCommonFood(foodName);
		}
		return food;
	}

	@Override
	public Food createSpicyFood(String foodName) {
		Food food = null;
		if (foodName.equals("FriedChicken")) {
			food = new FriedChicken();
			food.setName("辣的韓式炸雞");
		} else if (foodName.equals("KimChi")) {
			food = new KimChi();
			food.setName("辣的韓國泡菜");
		}
		return food;
	}

	@Override
	public Food createCommonFood(String foodName) {
		// TODO Auto-generated method stub
		Food food = null;
		if (foodName.equals("FriedChicken")) {
			food = new FriedChicken();
			food.setName("原味的韓式炸雞");
		} else if (foodName.equals("KimChi")) {
			food = new KimChi();
			food.setName("原味的韓國泡菜");
		}
		return food;
	}
}

FoodOrder作爲客戶類只需要涉及抽象工廠,不需要直接依賴任何產品對象,在實際使用時就可以根據對象類型來使用不同的抽象工廠的實現類。

public class FoodOrder {

	// 獲取請求,製作食品
	public void acceptOrder(AbsFactory absFactory) {
		Food food;// 食品對象
		String foodName = "";// 用戶輸入的類型
		String flavor = "";// 用戶輸入的口味
		do {
			flavor = getFlavor();
			foodName = getFoodName();
			food = absFactory.createFood(foodName, flavor);
			if (food != null) {
				food.prepare();
				food.make();
				food.box();
			} else {
				System.out.println("輸入的食品無供應");
			}
		} while (true);
	}

	private String getFoodName() {
		BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
		String foodName = null;
		System.out.println("輸入要訂購的食品——》:");
		try {
			foodName = bufferedReader.readLine();
		} catch (IOException e) {
			System.out.println("輸入有誤!");
			e.printStackTrace();
		}
		return foodName;
	}

	/*
	 * 獲取用戶口味
	 */
	private String getFlavor() {
		System.out.println("輸入你的口味偏好:(spicy or common)");
		Scanner scanner = new Scanner(System.in);
		String flavor = scanner.nextLine();
		return flavor;

	}

}

3.小結

  • 抽象工廠可以看出是簡單工廠模式和工廠方法模式的整合,從設計層面看,抽象工廠是對簡單工廠模式的改進。
  • 抽象工廠模式將工廠抽象成兩層:抽象工廠和具體實現的工廠子類。這樣就將單個的簡單工廠變成了工廠簇,更利於代碼的維護和擴展。

五、工廠模式應用實例

java.util包中的Calendar類中,就使用到了簡單工廠模式,該類的創建對象的方法都調用了createCalendar()方法。

   public static Calendar getInstance()
    {
        return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
    }
    public static Calendar getInstance(TimeZone zone)
    {
        return createCalendar(zone, Locale.getDefault(Locale.Category.FORMAT));
    }
    public static Calendar getInstance(Locale aLocale)
    {
        return createCalendar(TimeZone.getDefault(), aLocale);
    }

    public static Calendar getInstance(TimeZone zone,
                                       Locale aLocale)
    {
        return createCalendar(zone, aLocale);
    }

createCalendar()方法負責創建所有Calendar類型的對象。這與傳統的簡單工廠模式中將創建對象的職責移到工廠對象的方式,不同的是,Calendar類是由createCalendar()方法擔當創建對象的職責。

    private static Calendar createCalendar(TimeZone zone,
                                           Locale aLocale)
    {
        CalendarProvider provider =
            LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
                                 .getCalendarProvider();
        if (provider != null) {
            try {
                return provider.getInstance(zone, aLocale);
            } catch (IllegalArgumentException iae) {
                // fall back to the default instantiation
            }
        }
        Calendar cal = null;
        if (aLocale.hasExtensions()) {
            String caltype = aLocale.getUnicodeLocaleType("ca");
            if (caltype != null) {
                switch (caltype) {
                case "buddhist":
                cal = new BuddhistCalendar(zone, aLocale);
                    break;
                case "japanese":
                    cal = new JapaneseImperialCalendar(zone, aLocale);
                    break;
                case "gregory":
                    cal = new GregorianCalendar(zone, aLocale);
                    break;
                }
            }
        }
        if (cal == null) {
            if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
                cal = new BuddhistCalendar(zone, aLocale);
            } else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
                       && aLocale.getCountry() == "JP") {
                cal = new JapaneseImperialCalendar(zone, aLocale);
            } else {
                cal = new GregorianCalendar(zone, aLocale);
            }
        }
        return cal;
    }

六、總結

  1. 所有的工廠模式都是用來封裝對象的創建,通過減少應用程序和具體類之間的依賴來鬆耦合。工廠模式幫助我們針對接口編程,而不是針對具體類編程
  2. 工廠模式更多地體現的是依賴倒置原則(Dependence Inversion principle) ,注意以下三點指導方針,有助於避免在OO設計中違法依賴原則:
  • 變量不可以持有具體類的引用。將創建對象(使用new的動作)放到工廠的一個方法 。
  • 不要讓類繼承具體類,而是要繼承抽象類或實現接口
  • 不要覆蓋基類中已實現的方法
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章