工厂模式
工厂模式是一种创建型模式.核心思想就是将项目中的某类或某批物品的创建从类中脱离出来交由工厂类接管
我们从一个订购披萨的场景来介绍工厂模式的意义.下文的讲解涉及到UML类图的相关知识,如果对UML类图不了解,可以先浏览设计模式——UML类图.
想象一个场景:披萨店的订购披萨.每一个披萨店都需要有订购披萨的服务,根据用户下单情况来制作相应的披萨.正常情况下我们设计的代码中应该是在每个披萨店类内部创建订购披萨的方法,该方法首先需要根据用户的选择选择相对应的披萨,然后制作披萨.对应的UML类图如下:
即设定抽象类Pizza,每种具体的披萨类实现Pizza抽象类,不同的披萨店类聚合Pizza类,在各自的类中实现订购披萨(orderPizza)的方法.
简单工厂模式
简单工厂模式即将项目中的某类物品的创建逻辑交由一个简单的工厂类执行.
现在来分析订购披萨在传统代码下的缺陷.假设此时披萨的种类变多,增加了1种新的披萨:PetterPizza.因此不同的披萨店类都需要更新自己的orderPizza方法:添加订购PetterPizza的相关逻辑.这种拓展其实违背了ocp原则.因此效率比较低.
改进做法即为将创建披萨的逻辑交由一个简单工厂类来执行.UML类图如下:
即为创建简单工厂类(SimpleFactoryPizza)来执行根据用户的选择创建某类Pizza的逻辑,该类依赖于抽象类Pizza,不同的披萨店类聚合该简单工厂类.
当利用简单工厂模式创建订购披萨的程序需要扩展新的披萨种类时,只需要在SimpleFactoryPizza类中添加新品种披萨的订购逻辑即可,提高了效率
工厂方法模式
工厂方法模式大致思路和简单工厂模式类似,只是将工厂的类型从单一的一个对象(类)变为了一个对象簇.
假设我们需要考虑来自不同地方的逻辑,如北京,河北等.此时披萨的种类还需要根据来源不同分为更细的种类,因此披萨的种类会增加.而此时如果仍只使用单一的工厂类来执行创建披萨的逻辑,则会造成createPizza方法冗余,且违背了单一职责原则.
因此改进做法是将单一的工厂类变成对象簇,即创建一个抽象工厂类,根据不同的披萨种类创建不同的工厂,这些子工厂继承抽象工厂类形成一个簇.
UML类图如下:
即不同的工厂子类依赖于对应的披萨子类
抽象工厂模式
抽象工厂模式相当于是将简单工厂模式和工厂方法模式结合.抽象工厂模式对工厂方法模式中的不同工厂子类进行了优化合并:将具有同一特征的子工厂类合并,从而减少子工厂类的数量.
即将工厂方法模式中与不同种类的披萨子类一一对应的子工厂类按照地方进行合并,由原来的四个子工厂类合并为两个子工厂类:BeijingFactoryPizza和HebeiFactoryPizza.UML类图如下:
可以看到抽象工厂模式和工厂方法模式是非常类似的.而二者的最大区别在于工厂方法模式中的每一个子工厂类只能创建一种物品,而抽象工厂模式中的每一个子工厂类能够创建一类物品.
工厂模式实例
在Java中的Calendar类中的Calendar的getInstance方法即使用了简单工厂模式.在调用Calendar的getInstance方法获取实例时需要根据不同的时区来创建对应的日历对象.而创建对应的日历对象则是交给createCalendar(充当简单工厂)方法来创建
public static Calendar getInstance(TimeZone zone,
Locale aLocale)
{
return createCalendar(zone, aLocale);
}
// getInstance方法实际上调用的是简单工厂中的createCalendar方法
// 创建日期的任务由getInstance本身转交给简单工厂来完成,更易于拓展
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 no known calendar type is explicitly specified,
// perform the traditional way to create a Calendar:
// create a BuddhistCalendar for th_TH locale,
// a JapaneseImperialCalendar for ja_JP_JP locale, or
// a GregorianCalendar for any other locales.
// NOTE: The language, country and variant strings are interned.
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;
}
工厂模式说明
- 工厂模式的意义:通过将主项目中实例对象的创建转交给工厂类实现,达到主项目和依赖类的解耦,提高了项目的可扩展性和可维护性
- 工厂模式的注意事项:
- 在主项目创建实力对象时不要new,而是转交给工厂类实现
- 不要让类继承具体类,而是继承抽象类或实现接口(和依赖倒转原则一致)
- 不要覆盖基类中已实现的方法(对象簇中的方法由对应的子类创建)