模式介绍
定义了一个创建对象的类,由这个类来封装实例化对象的行为。
简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式。
模式图解
原理类图
类图说明
简单工厂模式包含以下角色:
- 抽象产品:定义了产品的规范,描述了产品的主要特性和功能。对应类图中的
Pizza抽象类。 - 具体产品:实现或者继承抽象产品的子类。对应类图中的
GreekPizza和CheesePizza实现类。 - 具体工厂:提供了创建产品的方法,调用者通过该方法来获取产品。对应类图中的
SimpleFactory类。
示例代码
package model;
/**
* desc:简单工程模式-bean类
* @Author:asyyr
*/
public abstract class Pizza {
protected String name;
public void setName(String name) {
this.name = name;
}
public abstract void prepare();
public void bake() {
System.out.println(name + " baking;");
}
public void cut() {
System.out.println(name + " cutting;");
}
public void box() {
System.out.println(name + " boxing;");
}
}
package model;
/**
* desc:简单工程模式-bean类
* @Author:asyyr
*/
public class GreekPizza extends Pizza {
@Override
public void prepare() {
System.out.println("Greek");
}
}
package model;
/**
* desc:简单工程模式-bean类
* @Author:asyyr
*/
public class CheesePizza extends Pizza {
@Override
public void prepare() {
System.out.println("Cheese");
}
}
package model;
import java.util.Objects;
/**
* desc:简单工程模式-核心类
* @Author:asyyr
*/
public class SimpleFactory<T> {
public <T> T createBean(String type) {
T t = null;
if (Objects.equals("greek", type)) {
t = (T) new GreekPizza();
} else if (Objects.equals("cheese", type)) {
t = (T) new CheesePizza();
} else {
throw new RuntimeException("type is valid");
}
return t;
}
}
package model;
/**
* desc:简单工程模式-测试类
* @Author:asyyr
*/
public class Test {
public static void main(String[] args) {
test("greek");
}
public static void test(String type) {
Pizza pizza = new SimpleFactory<Pizza>().createBean(type);
pizza.setName(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}
}
扩展
在开发中也有一部分人将工厂类中的创建对象的功能定义为静态的,这个就是静态工厂模式。
package model;
import java.util.Objects;
/**
* desc:静态=工厂模式-核心类
* @Author:asyyr
*/
public class SimpleFactory<T> {
public static <T> T createBean(String type) {
T t = null;
if (Objects.equals("greek", type)) {
t = (T) new GreekPizza();
} else if (Objects.equals("cheese", type)) {
t = (T) new CheesePizza();
} else {
throw new RuntimeException("type is valid");
}
return t;
}
}
实现原理
优点
封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避 免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户 代码修改的可能性,更加容易扩展。
缺点
当产品类有不同的接口种类时,工厂类需要判断在什么时候创建某种产品。这种对时机的判断和对哪一种具体产品的判断逻辑混合在一起,使得系统在将来进行功能扩展时较为困难,这一缺点在工厂方法模式中得到克服。
由于简单工厂模式使用静态方法作为工厂方法,而静态方法无法由子类继承,因此,工厂角色无法形成基于继承的等级结构。这一缺点会在工厂方法模式中得到克服。
增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”。
与其它模式的关系
单例模式
单例模式使用了简单工厂模式。换言之,单例类具有一个静态工厂方法提供自身的实例,一个抽象产品类同时是子类的工厂。
但是单例模式并不是简单工厂模式的退化情形,单例模式要求单例类的构造方法是私有的,从而客户端不能直接将之实例化,而必须通过这个静态工厂方法将之实例化,而且单例类自身是自己的工厂角色。换言之,单例类自己负责创建自身的实例。
单例类使用一个静态的属性存储自己的唯一实例,工厂方法永远仅提供这一个实例。
多例模式
多例模式是对单例模式的推广。多例模式与单例模式的共同之处在于它们都禁止外界直接将之实例化,同时通过静态工厂方法向外界提供循环使用的自身的实例。它们的不同在于单例模式仅有一个实例,而多例模式则可以有多个实例。
多例模式往往具有一个聚集属性,通过向这个聚集属性登记已经创建过的实例达到循环使用实例的目的。一般而言,一个典型的多例类具有某种内部状态,这个内部状态可以用来区分各个实例,而对应于每一个内部状态,都只有一个实例存在。
根据外界传入的参量,工厂方法可以查询自己的登记聚集,如果具有这个状态的实例已经存在,就直接将这个实例提供给外界;反之,就首先创建一个新的满足要求的实例,将之登记到聚集中,然后再提供给客户端。
备忘录模式
单例模式和多例模式使用一个属性或者聚集属性来登记所创建的产品对象,以便可以通过查询这个属性或者聚集属性找到并共享已经创建了的产品对象,这就是备忘录模式的应用。
MVC模式
MVC模式并不是严格意义上的设计模式,而是在更高层次上的架构模式。MVC模式可以分解成为几个设计模式的组合,包括合成模式、策略模式、观察者模式,也有可能会包括装饰模式、调停者模式、迭代子模式以及工厂方法模式等。
简单工厂模式所创建的对象往往属于一个产品等级结构,这个等级结构可以是 MVC 模式中的视图(View),而工厂角色本身可以是控制器(Controller)。一个 MVC 模式可以有一个控制器和多个视图。
换言之,控制器端可以创建合适的视图端,就如同工厂角色创建合适的对象角色一样;而模型端则可以充当这个创建过程的客户端。
如果系统需要有多个控制器参与这个过程的话,简单工厂模式就不适合了,应当考虑使用工程方法模式。
应用场景
JDK 中的 DateFormat
getDateInstance() 方法
如果仔细考察这个DateFormat类就会发现,它是一个抽象类,但是却提供了很多的静态工厂方法,比如getDateInstance()为某种本地日期提供格式化,它由三个重载的方法组成。
public static final DateFormat getDateInstance();
public static final DateFormat getDateInstance(int style);
public static final DateFormat getDateInstance(int style, Locale aLocale);
第一次接触这个类会有一些困惑,比如有的人会问,为什么一个抽象类可以有自己的实例,并通过几个方法提供自己的实例。实际上,一个抽象类不能有自己的实例,但DateFormat的工厂方法是静态方法,并不是普通的方法。
getDateInstance()方法并没有调用DateFormat的构造函数来提供自己的实例,作为一个工厂方法,getDateInstance()方法做了一些有趣的事情。它所做的事情基本上可以分成两部分:一是运用多态性;二是使用静态工厂方法。
SimpleDateFormat是抽象类DateFormat的具体子类,这就意味着SimpleDateFormat是一个DateFormat类型的子类型;而getDateInstance()方法完全可以返回SimpleDateFormat的实例,并且仅将它声明为DateFormat类型,这就是最纯正的多态性原则的运用。
getDateInstance()方法是一个静态方法。如果它是一个非静态的普通方法会怎样?要使用这个(非静态)方法,客户端必须首先取得这个类的实例或者其子类的实例。而这个类是一个抽象类,不可能有自己的实例,所以客户端就只好首先取得其具体子类的实例。如果客户端能够取得它的子类的实例,那么还需要这个工厂方法干什么呢?
显然,这里使用静态工厂方法是为了将具体子类实例化的工作隐藏起来,从而客户端不必考虑如何将具体子类实例化,因为抽象类DateFormat会提供它的合适的具体子类的实例。
这是一个简单工厂方法模式的绝佳应用。
针对抽象编程
这样做是利用具体产品类的超类类型将它的真实类型隐藏起来,其好处是提供了系统的可扩展性。如果将来有新的具体子类被加入到系统中来,那么工厂类可以将交给客户端的对象换成新的子类的实例,而对客户端没有任何影响。
这种将工厂方法的返还类型设置成抽象产品类型的做法,叫做针对抽象编程,这是依赖倒转原则(DIP)的应用。
本地时间
与本地日期的格式化相对应的是为了某种本地时间提供格式化,这一功能由三个重载的getTimeInstance()方法提供。
public static final DateFormat getTimeInstance();
public static final DateFormat getTimeInstance(int style);
public static final DateFormat getTimeInstance(int style, Locale aLocale);
显然它们所提供的也是其具体子类的实例,而不是自身的实例。因为它自身是一个抽象类,不可能有自己的实例。由于其子类必然是DateFormat的子类型,因此返还类型可以是DateFormat类型,这也是多态性的体现。