工厂方法模式
定义一个用来创建对象的接口,但让子类决定将那个类实例化。该模式就是让那个对象的实例化延迟到其子类进行。
在工行方法模式中,工厂父类只负责定义创建产品对象的公共接口,工行子类是负责具体的产品对象创建。
工厂模式的出现弥补了简单工厂的缺陷,在简单工厂模式中,如果随着产品的增加,同时需要修改工厂创建对象的逻辑,违背了开闭原则。工厂模式利用了多态性和里氏代换原则,更好的进行系统的扩展。
开闭原则:软件实体应该对扩展开放,对修改关闭。就是指在尽量不修改代码的情况下进行功能的扩展。
里氏代换原则:所有引用基类的地方必须能够透明的使用子类的对象。
在一个软件中,将一个基类对象替换成他的子类对象,程序不会产生任何异常,反之则不成立。里氏代换原则是实现开闭原则的重要方式之一,由于在使用基类对象的地方都可以使用子类对象,所以在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定子类类型,用子类对象替换父类对象。
在运用该原则时,应该尽量将父类设计为抽象类或者接口,让子类集成父类或者实现父接口,并重写父类方法。在运行时,子类对象替换父类对象。可以方便的扩展系统,又较少的改动代码。
工厂模式可以很好地体现出开闭原则和里氏代换原则。
模式结构
Product(抽象的产品):是所有产品对象的接口。一句话,这个抽象的产品集合了具体产品的共有的方法。
ConcreteProduct(具体的产品):是做具体的产品的业务处理,他与具体的产品工厂一一对应。所有的产品都需要重写Product的方法。一句话,具体产品是抽象产品的子类或者实现类。
Factory(抽象的工厂):在这个工厂中声明了工厂方法,该方法返回一个抽象的产品。抽象工厂是工厂方法的核心,所有的具体工厂都要实现该接口。一句话,抽象工厂里面工厂方法作用在于创建产品对象,它的返回值是抽象的产品。
ConcreteFactory(具体的工厂):它是抽象工厂的子类,重写了在抽象工厂声明的方法,并交由客户端调用,返回一个具体产品的实例。一句话:它是抽象工厂的子类,主要作用就是创建具体的产品对象(即返回值就是具体的产品)。
总结就是:有工厂与产品的一一对应关系,抽象工厂创建抽象产品,具体工厂创建具体产品。
举例说明
场景:在日志记录中,可能需要进行不同场景的日志记录,比如有时需要文件日志记录,有时需要数据库日志记录。此时为了尽可能的满足开闭原则,则可以使用工厂方法模式。
创建一个抽象的日志产品:日志不仅仅需要记录,有需要定期清理过期的日志。那么这两个方法可以抽取出来。
package com.gm.factory.method;
/**
* 日志产品,充当抽象的产品
*/
public interface LoggerProduct {
/**
* 记录日志,每个系统都会进行日志记录
*/
public void writeLog();
/**
* 清理日志,部分系统将遗留的日志进行清理
*/
public void cleanLog();
}
创建一个文件日志记录器,充当具体的产品。
package com.gm.factory.method;
/**
* 文件日志记录, 重当具体的产品
*/
public class FileLogger implements LoggerProduct {
/**
* 记录日志
*/
@Override
public void writeLog() {
System.out.println("文件日志记录完成");
}
/**
* 清理日志
*/
@Override
public void cleanLog() {
System.out.println("清理过期文件日志完成");
}
}
创建一个数据库日志记录器,充当具体的产品
package com.gm.factory.method;
public class DBLogger implements LoggerProduct {
/**
* 记录日志
*/
@Override
public void writeLog() {
System.out.println("数据库日志记录完成");
}
/**
* 清理日志
*/
@Override
public void cleanLog() {
System.out.println("清理数据库过期日志完成");
}
}
创建一个抽象的日志记录器工厂,充当抽象的工厂,专门创建文件记录器工厂。
package com.gm.factory.method;
/**
* 日志工厂,充当抽象的工厂
*/
public interface LoggerFactory {
/**
* 创建日志记录器,声明的工厂方法
* @return
*/
public LoggerProduct createLogger();
}
创建一个文件日志记录器工厂,专门创建文件记录器对象。
package com.gm.factory.method;
/**
* 文件记录器工厂,充当具体的工厂
*/
public class FileLggerFactory implements LoggerFactory {
/**
* 创建文件记录器, 创建具体的产品对象
* @return
*/
@Override
public LoggerProduct createLogger() {
return new FileLogger();
}
}
创建一个数据库日志记录器的工厂,专门创建数据源日志记录器对象。
package com.gm.factory.method;
/**
* 数据源产品工厂,充当具体的工厂
*/
public class DBLoggerFactory implements LoggerFactory {
/**
* 创建数据源日志记录器,创建具体的产品
* @return
*/
@Override
public LoggerProduct createLogger() {
return new DBLogger();
}
}
此时,我们有工厂与产品的一一对应关系,抽象日志工厂创建抽象日志产品,文件工厂创建文件产品,数据库工厂创建数据库产品。
客户端调用
package com.gm.factory.method;
public class Client {
public static void main(String[] args) throws Exception{
LoggerFactory factory = (LoggerFactory)Class.forName("com.gm.factory.method.FileLggerFactory").newInstance();
LoggerProduct loggerProduct = factory.createLogger();
//记录日志
loggerProduct.writeLog();
//有的系统需要删除日志
loggerProduct.cleanLog();
}
}
输出结果
文件日志记录完成
清理过期文件日志完成
优劣势
优势
1.在工厂方法中,工厂方法创建客户端所需用的产品,而且隐藏了产品实例化过程,客户端只需要关心那个工厂,而不用关心创建细节,更无需关系具体产品的类名。
2.工厂角色和产品角色的多态性设计,能够让工厂自主决定创建那个产品对象,创建产品对象的细节完全在具体工厂内部完成。
3.在扩展时,无需对工厂方法进行逻辑改动,只需要扩展具体的功能产品及其对应的具体工厂即可。
缺点:
1.在扩展时,不仅需要提供功能产品对象,还需要提供具体的创建该产品的工厂,增加系统的复杂度。
2.因为考虑到扩展性,引入了抽象层(抽象工厂角色,抽象产品角色),增加对系统的理解难度。
适用场景
1.客户端不需要知道具体对象的类名,只需要知道创建该对象的工厂即可。这个工厂可以在数据库或者配置文件中配置。
2.在对象创建比工厂方法多时,即可使用。易于扩展。利用多态和里氏代换原则,使得系统更好的扩展。
针对上章节中的简单工厂方法中的场景,如果需要增加支付系统,秒杀系统,账户系统等系统,则扩展性不够强,我们可以创建抽象的系统产品,创建对应的抽象的系统工厂,具体的用户系统,用户工厂,支付系统,支付工厂,账户系统,账户工厂等来进行改造,扩展。