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的动作)放到工厂的一个方法 。
  • 不要让类继承具体类,而是要继承抽象类或实现接口
  • 不要覆盖基类中已实现的方法
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章