工厂方法模式#
工厂方法模式是一种创建型设计模式,其在父类中提供一个创建对象的方法,允许子类决定实例化对象的实例。 亦称:虚拟构造函数、Virtual Constructor、Factory Method
核心思想:使用更高层次的类实现创建对象方法的抽象定义,通过子类继承的方式来实现不同种的对象实例的创建。
具体实例场景#
目前你正在运营一个物流运输调度系统,系统初期的业务主要集中在陆地运输,也就是使用货车运输的方式完成所有系统调度任务。系统逐渐扩大,,陆地运输不再满足业务需求,需要加入海上运输。但由于项目初期,全都使用货车运输,此时系统底层代码已经和货车运输深深耦合在一起,使得扩展难以完成。
场景分析#
为什么此时工厂方法模式就可以派上用场?
- 需要扩展的业务和原本业务的主要逻辑基本一致:运输对象可以抽象为
deliver
这一个行为。 - 正是由于可以抽象出相同的行为,那么此时工厂中的产品也就是成为了一个接口,一个相通的行为规范。
- 物流系统仍需要持续扩展,海陆空等不止有两种运输方式。
工厂方法模式建议使用特殊的工厂方法,代替对于对象构造函数的直接调用。工厂方法返回的对象一般称之为”产品”。
也就是说,对象并不是直接原地new
出来了的,而是使用工厂统一提供的产生对象的方法,返回一个对象实例。可能仅仅是改变了构造函数的位置而已。
但是现在子类已经可以通过重写工厂中的方法,返回对应的创建产品的类型了。
注意,这里工厂方法模式,还仅仅只是通过子类通过重写父类中的方法来实现产生多个对象实例。这样也会带来很多问题。
调用工厂方法的代码,被称为客户端代码(Client Code)。由于工厂的内部封装,客户端代码并不关心对象具体实现,并不关心是哪一个子类,客户端代码将所有工厂中的产品抽象为一个
product
,仅仅使用product
中需要的方法即可。
在该场景下,product
对应的就是Transport
运输实体,需要调用使用的抽象方法则是deliver
,运输方法。
整体架构#
- 首先,工厂中的产品必须定义好接口,所有由创建者或者创建者的子类创建的对象,都应该是实现这个接口的(该接口对这些对象都应该是通用的)。
- 创建者(Creator)定义好生产产品的方法,(可以是抽象方法,由子类实现;也可以是默认定义实现,等待子类进行方法重写)。
- 具体子类(ConcreteCreator)选择重写父类生产方法,或者调用默认实现。
- 注意:每次不一定都需要生成新的对象实例,可以引入缓存池、对象池或者其他来源的已有对象。
Creator的对象创建方法,最主要的职责并不是创建产品,一般来说,创建者类应包含了和产品相关的所有核心业务逻辑,工厂发方法将这些逻辑从具体产品类中分离出来。
适合使用场景#
当你在编写代码的过程中,不能够提前预知对象所有的确切类别以及依赖关系。
如果需要向应用中添加一种新产品,就只需要创建一个新的创建者子类,之后再根据该子类中相关的业务逻辑进行重写工厂方法。
如果希望用户能够扩展软件库,或者框架的内部组件。
如果希望通过复用现有对象来节省系统资源,而不是每次都重新创建一个新的对象。
最简单的复用现有创建对象的方法就是维护一个对象池了,但此时对象池应该维护在哪里?我们需要一个又能够创建新对象,还能够复用对象的方法——工厂方法。
优缺点#
- 可以避免创建者和具体产品之间的紧密耦合
- 单一职责原则
- 开闭原则:无需更改现有的客户端代码就可以实现新的扩展
- 需要创建大量的子类,代码可能会越来越复杂
代码实现#
实现一个跨平台的对话框UI,包括按钮的渲染等。
产品实现#
首先定义一个所有产品都通用的接口,接口中包含对所有产品都有意义的方法。
public interface Button {
void render();
}
public class WindowsButton implements Button {
@Override
public void render() {
System.out.println("Windows System render the button.");
}
}
public class HTMLButton implements Button {
@Override
public void render() {
System.out.println("HTML render the button.");
}
}
java创建者#
public class WindowsDialog extends Dialog {
@Override
public Button createButton() {
return new WindowsButton();
}
}
public class HTMLDialog extends Dialog {
@Override
public Button createButton() {
return new HTMLButton();
}
}
public abstract class Dialog {
// 所有功能的入口
public void renderWindow() {
Button btn = createButton();
// render the whole window
btn.render();
}
// 由具体创建者,也就是子类具体实现
public abstract Button createButton();
}
java客户端代码#
public class Demo {
private static Dialog dialog;
public static void main(String[] args) {
// 初始化配置
configure();
renderWindow();
}
// 取决于预先配置
private void configure() {
if (System.getProperty("os").equals("Windows 10")) {
dialog = new WindowsDialog();
} else {
dialog = new HTMLDialog();
}
}
static void renderWindow() {
dialog.renderWindow()
}
}
java总结#
写完了代码之后呢,就会发现工作方法模式的缺点其实非常明显。如果需要新增一个扩展,那么就需要修改多处。
- 新增一个产品子类
- 新增一个创建者子类
- 客户端预先配置配置代码需要修改 所以工厂方法模式,相当于是其他创建型模式的一个基础,相比起来很简单,但是不灵活。
- 在许多设计工作的初期都会使用工厂方法模式 (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、 原型模式或生成器模式 (更灵活但更加复杂)。
- 抽象工厂模式通常基于一组工厂方法, 但你也可以使用原型模式来生成这些类的方法。
- 你可以同时使用工厂方法和迭代器模式来让子类集合返回不同类型的迭代器, 并使得迭代器与集合相匹配。
- 原型并不基于继承, 因此没有继承的缺点。 另一方面, 原型需要对被复制对象进行复杂的初始化。 工厂方法基于继承, 但是它不需要初始化步骤。
- 工厂方法是模板方法模式的一种特殊形式。 同时, 工厂方法可以作为一个大型模板方法中的一个步骤。