一、工廠模式簡介
工廠模式是用來封裝對象的創建的,通過將創建對象的代碼提取出來,減少應用程序和具體類之間的依賴來達到鬆耦合的目的。這幫助了我們針對接口編程,而不是針對具體類編程。
下面通過模擬餐館訂餐來學習工廠模式,有以下需求:
- 菜有很多種,比如PekingDuck,DriedChicken等等
- 菜的製作分爲以下幾個步驟:prepare、make、box。
- 應用不同的工廠模式:簡單工廠模式、工廠方法模式、抽象工廠模式來模擬菜的製作過程。
下面首先使用簡單工廠模式來完成上面的需求
二、簡單工廠模式
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;
}
六、總結
- 所有的工廠模式都是用來封裝對象的創建,通過減少應用程序和具體類之間的依賴來鬆耦合。工廠模式幫助我們針對接口編程,而不是針對具體類編程。
- 工廠模式更多地體現的是依賴倒置原則(Dependence Inversion principle) ,注意以下三點指導方針,有助於避免在OO設計中違法依賴原則:
- 變量不可以持有具體類的引用。將創建對象(使用new的動作)放到工廠的一個方法 。
- 不要讓類繼承具體類,而是要繼承抽象類或實現接口
- 不要覆蓋基類中已實現的方法