CH02-创建型-工厂方法

模式动机

针对“1-简单工厂”中提到的系统进行修改,不再使用一个按钮工厂类来统一负责所有产品的创建,而是将具体按钮的创建过程交给专门的工厂子类去完成。首先定义一个抽象的按钮工厂类,再定义具体的工厂类来生成圆形、矩形、菱形按钮等,这些具体的工厂类会实现抽象工厂类中定义的方法。

这种抽象化的结构可以在不修改具体工厂类的情况下引入新的产品,如果出现新的按钮类型,只需要为这种新的按钮类型创建对应的工厂类即可获得该新按钮的实例。因此,使得工厂方法模式具有超越简单工厂的优越性,更加符合开闭原则

模式定义

**工厂方法模式(Factory Method Pattern)**又称工厂模式,或虚拟构造器(Virtual Constructor)模式、多态工厂(Polymorphic Factory),属于类创建型模式。该模式中,工厂父类负责负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪个具体产品类。

模式结构

工厂方法包含的角色:

  1. Product:抽象产品
  2. ConcreteProduct:具体产品
  3. Factory:抽象工厂
  4. ConcreteFactory:具体工厂

类图

NAME

时序图

NAME

代码示例

#include "ConcreteFactory.h"
#include "ConcreteProduct.h"

Product* ConcreteFactory::factoryMethod(){

	return  new ConcreteProduct();
}
#include "Factory.h"
#include "ConcreteFactory.h"
#include "Product.h"
#include <iostream>
using namespace std;

int main(int argc, char *argv[])
{
	Factory * fc = new ConcreteFactory();
	Product * prod = fc->factoryMethod();
	prod->use();
	
	delete fc;
	delete prod;
	
	return 0;
}

模式分析

由于使用了面向对象的多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。在该模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。这个核心类仅仅负责给出具体工厂必须实现的接口,而不负责哪一个产品类被实例化这个细节,使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新产品。

实例

日志记录器

某系统日志记录器要求支持多种日志记录方式,如文件、数据库等。且用户可以根据要求动态选择日志记录方式,使用工厂方法进行设计:

类图:

NAME

时序图:

NAME

优点

  • 该模式中,工厂方法用来创建用户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无需关心创建细节,甚至无需知道具体产品类的类名。
  • 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。
  • 在加入新产品时,无需修改抽象工厂和抽象产品提供的接口,无需修改客户端,也无需修改其他的具体工厂和具体产品,只需要添加一个具体工厂和具体产品就可以了。增加了扩展性,符合开闭原则。

缺点

  • 在添加新产品时需要编写新的产品类,还要提供于此对应的具体工厂类,类的个数会成对增加,一定程度上增加了复杂性。
  • 引入了抽象层,客户端的代码均使用抽象层进行定义,增加了系统的抽象性和理解难度。

适用场景

  • 无需知道需要创建对象的类:该模式中,客户端不需要知道具体产品类的类名,只需要知道他对应的工厂即可,具体的产品对象由具体工厂类创建。
  • 通过子类来指定创建哪个对象:该模式中,对于抽象工厂类只需要提供一个创建产品的接口,由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏替换原则,在程序运行时,子类对象将覆盖父类对象,从而使系统更易扩展。
  • 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时无需关心是哪个工厂子类来创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。

模式应用

JDBC 中的工厂方法:

Connection conn=DriverManager.getConnection("jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=DB;user=sa;password=");
Statement statement=conn.createStatement();
ResultSet rs=statement.executeQuery("select * from UserInfo");

模式扩展

  • 使用多个工厂方法:才抽象工厂中可以定义多个工厂方法,从而使具体工厂角色实现这些不同的工厂方法,这些方法可以包含不同的业务逻辑,以满足不同的产品对象的需求。
  • 产品对象的重复使用:工厂对象将已创建过的产品保存到一个集合,然后根据客户端对产品的请求,对集合进行查询。
  • 多态性的丧失和模式的退化:如果工厂仅仅返回一个具体产品对象,便违背了工厂方法的用意,发生退化,此时就不再是工厂方法模式了。工厂对象应该有一个抽象的父类型,如果工厂等级结构中只有一个工厂类的话,抽象工厂可以省略,则发生退化;当只有一个具体工厂,在具体工厂中创建所有产品对象,并且工厂方法设计为静态方法时,该模式就退化成了简单工厂模式。

总结

  • 工厂方法模式又称为工厂模式,属于类创建型模式。该模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,以将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。
  • 工厂方法模式包含四个角色:
    1. 抽象产品是定义产品的接口,是工厂方法模式所创建对象的超类型,即产品对象的共同父类或接口;
    2. 具体产品实现了抽象产品接口,某种类型的具体具体产品由专门的具体工厂创建,他们之间往往一一对应
    3. 抽象工厂中声明了工厂方法,用于返回一个产品,它是该模式的核心,任何在模式中创建对象的工厂类都必须实现该接口;
    4. 具体工厂是抽象工厂的子类,实现了抽象工厂中定义的方法,并可由客户调用,返回一个具体产品类的实例。
  • 该模式是简单工厂模式的进一步抽象和推广。由于使用了面向对象的多态性,工厂方法模式保持了简单工厂模式的优点,同时克服了它的缺点。核心的工厂类不再负责创建所有类型的产品,而是将其创建交给具体子类去做。该核心类仅负责给出具体工厂必须实现的接口,而不负责产品类被实例化这种细节,这使得该模式可以允许系统在不修改工厂角色的情况下引进新产品。
  • 主要优点是增加新产品而无需修改现有系统,并封装了产品对象的创建细节,系统具有良好的灵活性和可扩展性;缺点在于增加新产品的同时需要增加对应的具体工厂,导致类的个数增长,一定程度上增加了系统的复杂性。
  • 该模式适合的情况包括:一个类不知道他所需要的对象的类;一个类通过其子类来指定创建哪个对象;将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无需关心是哪一个工厂子类创建产品子类,需要时再动态绑定。