工厂方法模式
简单工厂模式虽然简单,但存在一个很严重的问题当系统中需要引入新产品时,由于静态
工厂方法通过所传入参数的不同来创建不同的产品,这必定要修改工厂类的源代码,将违
背“开闭原则”
在简单工厂模式中,所有的产品都由同一个工厂创建,工厂类职
责较重,业务逻辑较为复杂,具体产品与工厂类之间的耦合度高,严重影响了系统的灵活性
和扩展性,而工厂方法模式则可以很好地解决这一问题。
在工厂方法中
不在提供一个统一的工厂类来创建所有的产品对象
针对不同的产品提供不同的工厂
系统提供一个与产品等级结构对应的工厂等级结构
定义:
定义一个用于创建对象的接口,让子类决定将哪一个
类实例化。工厂方法模式让一个类的实例化延迟到其子类。
工厂方法模式又简称为工厂模式又可称作虚拟构造器模式或多态工厂模式
工厂方法模式是一种类创建型模式。
图示:
角色:
Product(抽象产品):它是定义产品的接口,是工厂方法模式所创建对象的超类型,
是所有产品的公共父类
ConcreteProduct(具体产品):它实现了抽象产品接口,某种类型的具体产品由专门的具体
工厂创建,具体工厂和具体产品之间一一对应。
Factory(抽象工厂):在抽象工厂类中,声明了工厂方法(Factory Method),用于返回一个
产品。抽象工厂是工厂方法模式的核心,所有创建对象的工厂类都必须实现该接口。
interface Factory { public Product factoryMethod(); }
声明了工厂方法但并未实现工厂方法,具体产品对象的创建由其子类负责,客
户端针对抽象工厂编程,可在运行时再指定具体工厂类
ConcreteFactory(具体工厂):它是抽象工厂类的子类,实现了抽象工厂中定义的工厂方
法,并可由客户端调用,返回一个具体产品类的实例。
class ConcreteFactory implements Factory { public Product factoryMethod() { return new ConcreteProduct(); } }
事例:
日志记录器的设计
记录器可以通过多种途径保存系
统的运行日志,如通过文件记录或数据库记录,用户可以通过修改配置文件灵活地更换日志
记录方式
开发人员发现需要对日志记录器进行一些
初始化工作,初始化参数的设置过程较为复杂,而且某些参数的设置有严格的先后次序,否
则可能会发生记录失败。
需求分析:
(1) 需要封装日志记录器的初始化过程,这些初始化工作较为复杂,例如需要初始化其他相关
的类,还有可能需要读取配置文件(例如连接数据库或创建文件),导致代码较长,如果将
它们都写在构造函数中,会导致构造函数庞大,不利于代码的修改和维护;
(2) 用户可能需要更换日志记录方式,在客户端代码中需要提供一种灵活的方式来选择日志记
录器,尽量在不修改源代码的基础上更换或者增加日志记录方式。
class LoggerFactory { //静态工厂方法 public static Logger createLogger(String args) { if(args.equalsIgnoreCase("db")) { //连接数据库,代码省略 //创建数据库日志记录器对象 Logger logger = new DatabaseLogger(); //初始化数据库日志记录器,代码省略 return logger; } else if(args.equalsIgnoreCase("file")) { //创建日志文件 //创建文件日志记录器对象 Logger logger = new FileLogger(); //初始化文件日志记录器,代码省略 return logger; } else { return null; } } }
存在的问题:
(1) 工厂类过于庞大,包含了大量的if…else…代码,导致维护和测试难度增大;
(2) 系统扩展不灵活,如果增加新类型的日志记录器,必须修改静态工厂方法的业务逻辑,违
反了“开闭原则”。
解决方案:
//抽象产品 //日志纪录器的接口 public interface Logger { public void writeLog(); }
//具体产品 //文件日志记录器 public class FileLogger implements Logger { @Override public void writeLog() { System.out.println("文件日志记录"); } }
//具体产品 //数据库日志记录器 public class DataBaseLogger implements Logger { @Override public void writeLog() { System.out.println("数据库日志记录"); } }
//抽象工厂 public interface LoggerFactory { public Logger createLogger(); }
//具体工厂 //数据库日志记录器的工厂类 public class DataBaseLoggerFactory implements LoggerFactory { @Override public Logger createLogger() { Logger logger = new DataBaseLogger(); return logger; } }
//具体工厂 //文件日志记录器的工厂类 public class FileLoggerFactory implements LoggerFactory { @Override public Logger createLogger() { Logger logger = new FileLogger(); return logger; } }
测试:
public class client { public static void main(String[] args) { LoggerFactory factory; Logger logger; factory = new FileLoggerFactory(); logger = factory.createLogger(); logger.writeLog(); } }
此时的结构图:
重载工厂方法:
开发人员通过进一步分析,发现可以通过多种方式来初始化日志记录器,例如可以
为各种日志记录器提供默认实现;还可以为数据库日志记录器提供数据库连接字符串,为文
件日志记录器提供文件路径;也可以将参数封装在一个Object类型的对象中,通过Object对象
将配置参数传入工厂类。
//抽象工厂 public interface LoggerFactory { public Logger createLogger(); public Logger createLogger(String msg); }
//具体工厂 //文件日志记录器的工厂类 public class FileLoggerFactory implements LoggerFactory { @Override public Logger createLogger() { Logger logger = new FileLogger(); return logger; } @Override public Logger createLogger(String msg) { //使用参数msg斤西瓜连接数据库 //....... Logger logger = new FileLogger(); return logger; } }
//具体工厂 //数据库日志记录器的工厂类 public class DataBaseLoggerFactory implements LoggerFactory { @Override public Logger createLogger() { Logger logger = new DataBaseLogger(); return logger; } @Override public Logger createLogger(String msg) { //使用参数msg斤西瓜连接数据库 //...... Logger logger = new DataBaseLogger(); return logger; } }
优点:
(1) 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体
产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚
至无须知道具体产品类的类名。
(2) 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够让工厂可以自主确
定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模
式之所以又被称为多态工厂模式,就正是因为所有的具体工厂类都具有同一抽象父类。
(3) 使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品
提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具
体工厂和具体产品就可以了,这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。
缺点:
(1) 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统
中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,
会给系统带来一些额外的开销。
(2) 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,
增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统
的实现难度
适用场景:
(1) 客户端不知道它所需要的对象的类。在工厂方法模式中,客户端不需要知道具体产品类的
类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建,可将具体工厂类
的类名存储在配置文件或数据库中。
(2) 抽象工厂类通过其子类来指定创建哪个对象。在工厂方法模式中,对于抽象工厂类只需要
提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和
里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。