一、工厂模式简介
工厂模式是用来封装对象的创建的,通过将创建对象的代码提取出来,减少应用程序和具体类之间的依赖来达到松耦合的目的。这帮助了我们针对接口编程,而不是针对具体类编程。
下面通过模拟餐馆订餐来学习工厂模式,有以下需求:
- 菜有很多种,比如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的动作)放到工厂的一个方法 。
- 不要让类继承具体类,而是要继承抽象类或实现接口
- 不要覆盖基类中已实现的方法