Java工厂模式详解
@author:Jingdai
@date:2021.05.24
工厂模式只包括两个,一个是工厂方法(Factory Method)模式,一个是抽象工厂(Abstract Factory)模式,但是我们平时也经常听到简单工厂模式和静态工厂模式,它们有什么关系呢?希望这篇文章能解决你的问题。
简单工厂、静态工厂
首先说明,简单工厂并不属于23种设计模式中的某一种,它更像是一种编程习语。
本文中的例子部分来自于深入浅出设计模式,看下面这个例子。
Pizza orderPizza(String type) {
Pizza pizza;
if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("greek")) {
pizza = new GreekPizza();
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
为了更好的灵活性,使orderPizza() 方法的返回值 Pizza 是一个抽象类,这样就可以返回任何 Pizza 类的子类。但是,由上面代码可见,虽然 Pizza 类是一个抽象类,但是我们实例化的时候还是必须实例化一个Pizza的子类,因为 Java 中抽象类和接口是不能实例化的,所以这里只能通过 new Pizza的子类进行创建对象并返回,也就是说这里的orderPizza() 方法仍然依赖于具体的子类,而不是依赖于抽象类。如果今后又有了新的Pizza品种,比如ClamPizza,那么就需要改变 orderPizza() 方法,这就违反了开闭原则(对拓展开放,对修改关闭)。当然如果如果你明确知道一个类以后不会拓展,那就不会有任何问题,也就不需要使用工厂模式。那简单工厂是怎么解决这个问题的呢?其实简单工厂就是将Pizza对象的创建封装了起来,看下面利用简单工厂实现的代码。
PizzaStore
public class PizzaStore {
SimplePizzaFactory factory;
public PizzaStore(SimplePizzaFactory factory) {
this.factory = factory;
}
public Pizza orderPizza(String type) {
Pizza pizza;
pizza = factory.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
// other methods here
}
SimplePizzaFactory
public class SimplePizzaFactory {
public Pizza createPizza(String type) {
Pizza pizza = null;
if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("greek")) {
pizza = new GreekPizza();
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
}
return pizza;
}
}
这样就完成了一个简单工厂的设计,这样看的话仅仅是把变化的部分从一个地方移动到了另一个地方,并没有解决什么问题,但是要注意,我们这里仅仅只有一个 orderPizza() 方法使用了工厂,但是实际上很可能还有很多地方都使用到这个工厂,以后需要创建Pizza的地方仅仅需要依赖于简单工厂,把创建Pizza的任务交给简单工厂,这样一来以前需要在各个地方修改代码,而以后只需要在简单工厂的代码中修改就行了。
那静态工厂又是什么呢?看上面的代码,每次我们需要pizza都需要创建一个SimplePizzaFactory对象,可以直接把这个创建pizza的方法改为静态的,那样的话之后再需要pizza就不需要创建工厂对象了,直接调用静态方法就行了,这样的简单工厂就称为静态工厂。
再次强调一下,简单工厂和静态工厂并不属于23种设计模式,它还是没有解决违反开闭原则的问题。
工厂方法模式
还是上面的需求,这次我们用工厂方法模式去解决Pizza需求经常变化的问题。我们可以对每一种Pizza都创建一个具体的工厂,用具体的工厂去创建对应的Pizza。
既然有多种工厂,那就需要一个工厂基类,多个工厂类去继承这个工厂基类。同时,由于Pizza的类型已经由工厂类型决定了,所以createPizza() 方法不再需要 type 参数。
改变后的代码如下。
PizzaStore
public class PizzaStore {
PizzaFactory factory;
public PizzaStore(PizzaFactory factory) {
this.factory = factory;
}
public Pizza orderPizza() {
Pizza pizza;
pizza = factory.createPizza();
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
PizzaFactory
public abstract class PizzaFactory {
public abstract Pizza createPizza();
}
CheesePizzaFactory
public class CheesePizzaFactory extends PizzaFactory{
@Override
public Pizza createPizza() {
return new CheesePizza();
}
}
上面只举了一个CheesePizzaFactory的例子,其它的Pizza对应的工厂也类似。这样写完之后就实现了工厂方法模式。再回头看 PizzaStore 这个类,它全部依赖于抽象,Pizza是抽象的,PizzaFactory也是抽象的,它对之后具体是什么工厂创建什么Pizza一无所知,这些工作完全交给了具体的工厂子类。当传递一个CheesePizzaFactory时,它就是CheesePizza;当传递一个GreekPizzaFactory时,它就是GreekPizza,其他同理。也就是说他把对象的实例化交给了子类,即延迟到子类实现,完全由子类决定具体创建的对象。
再看看工厂方法是否满足我们的要求。当我们增加了一个Pizza需求,比如ClamPizza,这时我们只需要加一个ClamPizza类和一个ClamPizzaFactory类就行,PizzaStore类完全不需要任何改动,满足开闭原则。当然,使用PizzaStore类的那个类肯定是需要修改的,需要将参数改为ClamPizzaFactory的对象,这就不是我们关心的内容了,由使用它的类去完成相应的需求。
下面看工厂方法的官方定义。
工厂方法模式定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
对照上面的例子估计就不难理解了。
抽象工厂模式
首先看下面这个需求,我们的一个订单Dao需要与数据库进行交互,要创建3个对象,Connection、Statement和ResultSet,利用上面讲的工厂方法模式,可以写出下面的代码,下面仅写了Connection相关的代码,Statement和ResultSet类似。
OrderDao
public class OrderDao {
ConnectionFactory connectionFactory;
StatementFactory statementFactory;
ResultSetFactory resultSetFactory;
public OrderDao(ConnectionFactory connectionFactory,
StatementFactory statementFactory,
ResultSetFactory resultSetFactory) {
this.connectionFactory = connectionFactory;
this.statementFactory = statementFactory;
this.resultSetFactory = resultSetFactory;
}
public Order getOrderById(Integer id) {
Connection connection = connectionFactory.createConnection();
Statement statement = statementFactory.createStatement();
ResultSet resultSet = resultSetFactory.createResultSet();
// some xxx operations
return order;
}
}
Connection
public abstract class Connection {
// xxx
}
OracleConnection
public class OracleConnection extends Connection{
// xxx
}
ConnectionFactory
public abstract class ConnectionFactory {
public abstract Connection createConnection();
}
OracleConnectionFactory
public class OracleConnectionFactory extends ConnectionFactory{
@Override
public Connection createConnection() {
return new OracleConnection();
}
}
仔细观察上面的代码,好像也没什么问题,但是其实是有一些问题的,OrderDao使用的时候,会使用三个具体的工厂实例,比如用Oracle的数据库,那就是OracleConnectionFactory、OracleStatementFactory和OracleResultSetFactory,这三个工厂创建的三个实例OracleConnection、OracleStatement和OracleResultSet是相互关联的,即同一数据库的三个类应该是一致的,比如OracleConnection肯定是不能与MySQLStatement相互操作。那怎么解决这个问题呢?由于这三个类是相互关联的,是一个家族的,那么我们就应该用一个工厂来创建这三个对象,修改后的代码如下。
OrderDao
public class OrderDao {
DBFactory dbFactory;
public OrderDao(DBFactory dbFactory) {
this.dbFactory = dbFactory;
}
public Order getOrderById(Integer id) {
Connection connection = dbFactory.createConnection();
Statement statement = dbFactory.createStatement();
ResultSet resultSet = dbFactory.createResultSet();
// some xxx operations
return order;
}
}
DBFactory
public abstract class DBFactory {
public abstract Connection createConnection();
public abstract Statement createStatement();
public abstract ResultSet createResultSet();
}
OracleDBFactory
public class OracleDBFactory extends DBFactory{
@Override
public Connection createConnection() {
return new OracleConnection();
}
@Override
public Statement createStatement() {
return new OracleStatement();
}
@Override
public ResultSet createResultSet() {
return new OracleResultSet();
}
}
Connection
public abstract class Connection {
// xxx
}
OracleConnection
public class OracleConnection extends Connection{
// xxx
}
这样就完成了抽象工厂模式,由于这里只有一个工厂,当我们用一个OracleDBFactory实例化这个变量后,它创建的三个对象肯定是Oracle相关的三个对象,这样就解决了上面的三个相关联的对象的创建问题。
可以看出,抽象工厂模式和工厂方法模式非常相似,抽象工厂里面的每个方法的实现其实就是一个工厂方法模式,它的优点就是可以创建一系列相互依赖的对象,如果抽象工厂里只有一个创建对象的方法,那抽象工厂模式就变成了一个工厂方法模式。
下面看抽象工厂的官方定义。
抽象工厂提供了一个接口,让该接口负责创建一系列相关/相互依赖的对象,而无需指定它们具体的类。
总结
- 不管是简单工厂、工厂方法还是抽象工厂,它们都是通过某种方法绕开 new 操作,避免 new 操作带来的紧耦合,将对象的实例化移到本方法之外。
- 如果没有多个相关联的对象需要创建,就没有必要使用抽象工厂模式,仅仅使用工厂方法模式就可以。
参考
- 深入浅出设计模式