工厂模式的应用场景很多,到底再那些地方用过呢?为什么需要使用?有什么好处?本文将从应用场景出发逐步揭开工厂模式背后的面纱。
先对工厂模式做一个大概的说明,其定义:
Define an interface for creating an object,but let subclasses decide which class to instantiate.Factory Method lets a class defer instantiation to subclasses.(定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。)
有了一个大概的概念后,我们来列举一下其应用场景:
- Spring获取Bean,通过BeanFacotory工厂接口实现;
- Log日志,通过LoggerFactory获取日志对象;
- jdbc,创建数据库连接;
Spring获取bean的代码如下:
# 通过xml获取Bean
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean-factoryMethod.xml");
DemoApplication bean = ctx.getBean(DemoApplication.class);
# 通过DI的方式注入Bean
@Autowired
DemoApplication demoApplication;
# 通过DI的方式注入Bean
@Service
DemoApplication demoApplication;
接下来我们看看ClassPathXmlApplicationContext是怎么实现的,先看到下面的类结构图:
BeanFactory接口存在四种工厂SimpleJndiBeanFactory、HierachicalBeanFacory、AutowireCapleBeanFactory、ListableBeanFactory,这四个工厂也对应了获取Bean的不同方式比如通过Jndi获取bean就使用SimpleJndiBeanFactory工厂,通过xml获取bean就通过HierachicalBeanFacory工厂下的实现,通过依赖注入的方式就通过AutowireCapleBeanFactory工厂。
Spring中有一个最大的功能就是DI(依赖注入),所依赖的对象一般都是接口,这也吻合了设计原则中的依赖倒置原则(DIP),而创建bean与之相关的也都交给了Spring容器来管理,也就是不需要关注bean的创建过程以及与之相关的其他对象,也就是设计原则中的迪米特法则(LOD),在这里我们所有的接口也可以使用其子类来进行替换,这也就吻合里氏替换原则(LSP)。
Spring创建bean的使用的设计模式即为抽象工厂模式,需要实现不同创建bean方式只需要继承BeanFactory接口进行实现。
抽象工厂定义:
抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。
在最早学习Java的时候,JDBC获取数据库连接,实现代码如下:
import java.sql.Connection;
import java.sql.SQLException;
public class ConnectionFactory {
public static Connection getConnection() {
Connection conn = null;
try {
conn = DataSourceHolder.getInstance().getDataSource()
.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
}
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSource;
public class DataSourceHolder {
private BasicDataSource ds = new BasicDataSource();
private DataSourceHolder() {
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/dbo");//dbo为连接的数据库名称
ds.setUsername("root");//账号
ds.setPassword("root");//密码
}
private static class SingletonHolder{
private static DataSourceHolder instance = new DataSourceHolder();
}
public static DataSourceHolder getInstance(){
return SingletonHolder.instance;
}
public DataSource getDataSource(){
return ds;
}
}
由于获取数据连接需要比较多的参数设置,其过程还是相对比较复制,因此使用ConnectionFactory数据库连接的工厂,非常简单的就可以获取数据库连接对象(使用ConnectionFactory .getConnection())。
以上这种使用静态方法获取数据库连接的方式就是静态工厂模式,看到这种方式是不是可以想到我们之前实现的很多代码都非常相似,比如时间工具类、Json工具类,其实这些都归纳为静态工厂或者称之为简单工厂。
简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
接下来我们看看Logger的实现原理:
获取logger的对象通常会通过以下方式获取
org.slf4j.Logger logger = LoggerFactory.getLogger("DemoApplication");
查询getLogger源码:
public final class LoggerFactory {
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
public static ILoggerFactory getILoggerFactory() {
if (INITIALIZATION_STATE == UNINITIALIZED) {
synchronized (LoggerFactory.class) {
if (INITIALIZATION_STATE == UNINITIALIZED) {
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
performInitialization();
}
}
}
switch (INITIALIZATION_STATE) {
case SUCCESSFUL_INITIALIZATION:
return StaticLoggerBinder.getSingleton().getLoggerFactory();
case NOP_FALLBACK_INITIALIZATION:
return NOP_FALLBACK_FACTORY;
case FAILED_INITIALIZATION:
throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
case ONGOING_INITIALIZATION:
// support re-entrant behavior.
// See also http://jira.qos.ch/browse/SLF4J-97
return SUBST_FACTORY;
}
throw new IllegalStateException("Unreachable code");
}
}
很容易就可以判别出Logger使用了静态工厂,我们可以看到getILoggerFactory()方法,在这里根据logger不同配置参数创建了不同工厂,而这里所有的工厂都是实现了ILoggerFactory接口,因此在日志这个组件中也是用了抽象工厂,因此我们可以看出不管是Spring或者日志组件都使用了抽象工厂来增强其扩展性,这也就体现了抽象工厂最大的特性就是可扩展性强,也吻合开闭原则对扩展开发对修改关闭。
接下来我们看看这样的一段代码:
产品
public abstract class Product {
//产品类的公共方法
public void method1(){
//业务逻辑处理
}
//抽象方法
public abstract void method2();
}
public class ConcreteProduct1 extends Product {
public void method2() {
//业务逻辑处理
}
}
public class ConcreteProduct2 extends Product {
public void method2() {
//业务逻辑处理
}
}
抽象工厂
public abstract class Creator {
/*
* 创建一个产品对象,其输入参数类型可以自行设置
* 通常为String、Enum、Class等,当然也可以为空
*/
public abstract <T extends Product> T createProduct(Class<T> c);
}
public class ConcreteCreator extends Creator {
public <T extends Product> T createProduct(Class<T> c){
Product product=null;
try {
product = (Product)Class.forName(c.getName()).newInstance();
} catch (Exception e) {
//异常处理
}
return (T)product;
}
}
场景类
public class Client {
public static void main(String[] args) {
Creator creator = new ConcreteCreator();
Product product = creator.createProduct(ConcreteProduct1.class);
/*
* 继续业务处理
*/
}
}
以上是通过产品名称创建对应的产品的过程,这也是很多资料中介绍工厂模式时所使用的示例,这也就时工厂方法模式最基本的实现。
总结:
(1)简单工厂(Simple Factory)模式,又称静态工厂方法模式(Static Factory Method Pattern),一般使用静态方法进行实现。
(2)工厂方法(Factory Method)模式,又称多态性工厂(Polymorphic Factory)模式或虚拟构造子(Virtual Constructor)模式,Define an interface for creating an object,but let subclasses decide which class to instantiate.Factory Method lets a class defer instantiation to subclasses.(定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。);
(3)抽象工厂(Abstract Factory)模式,Provide an interface for creating families of related or dependent objects without specifying their concrete classes.(为创建一组相关或相互依赖的对象提供一个接口,而且无需指定它们的具体类。)。
优点:
- 首先,良好的封装性,代码结构清晰。一个对象创建是有条件约束的,如一个调用者需要一个具体的产品对象,只要知道这个产品的类名(或约束字符串)就可以了,不用知道创建对象的艰辛过程,降低模块间的耦合。
- 其次,工厂方法模式的扩展性非常优秀。在增加产品类的情况下,只要适当地修改具体的工厂类或扩展一个工厂类,就可以完成“拥抱变化”。例如在我们的例子中,需要增加一个棕色人种,则只需要增加一个BrownHuman类,工厂类不用任何修改就可完成系统扩展。
- 再次,屏蔽产品类。这一特点非常重要,产品类的实现如何变化,调用者都不需要关心,它只需要关心产品的接口,只要接口保持不变,系统中的上层模块就不要发生变化。因为产品类的实例化工作是由工厂类负责的,一个产品对象具体由哪一个产品生成是由工厂类决定的。在数据库开发中,大家应该能够深刻体会到工厂方法模式的好处:如果使用JDBC连接数据库,数据库从MySQL切换到Oracle,需要改动的地方就是切换一下驱动名称(前提条件是SQL语句是标准语句),其他的都不需要修改,这是工厂方法模式灵活性的一个直接案例。
- 最后,工厂方法模式是典型的解耦框架。高层模块值需要知道产品的抽象类,其他的实现类都不用关心,符合迪米特法则,我不需要的就不要去交流;也符合依赖倒置原则,只依赖产品类的抽象;当然也符合里氏替换原则,使用产品子类替换产品父类,没问题!
使用场景:
- 首先,工厂方法模式是new一个对象的替代品,所以在所有需要生成对象的地方都可以使用,但是需要慎重地考虑是否要增加一个工厂类进行管理,增加代码的复杂度。
- 其次,需要灵活的、可扩展的框架时,可以考虑采用工厂方法模式。万物皆对象,那万物也就皆产品类,例如需要设计一个连接邮件服务器的框架,有三种网络协议可供选择:POP3、IMAP、HTTP,我们就可以把这三种连接方法作为产品类,定义一个接口如IConnectMail,然后定义对邮件的操作方法,用不同的方法实现三个具体的产品类(也就是连接方式)再定义一个工厂方法,按照不同的传入条件,选择不同的连接方式。如此设计,可以做到完美的扩展,如某些邮件服务器提供了WebService接口,很好,我们只要增加一个产品类就可以了。
- 再次,工厂方法模式可以用在异构项目中,例如通过WebService与一个非Java的项目交互,虽然WebService号称是可以做到异构系统的同构化,但是在实际的开发中,还是会碰到很多问题,如类型问题、WSDL文件的支持问题,等等。从WSDL中产生的对象都认为是一个产品,然后由一个具体的工厂类进行管理,减少与外围系统的耦合。
- 最后,可以使用在测试驱动开发的框架下。例如,测试一个类A,就需要把与类A有关联关系的类B也同时产生出来,我们可以使用工厂方法模式把类B虚拟出来,避免类A与类B的耦合。目前由于JMock和EasyMock的诞生,该使用场景已经弱化了,读者可以在遇到此种情况时直接考虑使用JMock或EasyMock。
笔者的微信公众号,每天一篇好文章: