简单工厂概述
工厂模式是使用频率很高的一类创建型模式,通常所说的工厂模式是指工厂方法模式。简单工厂模式(Simple Factory Pattern):定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都有共同的父类。使用简单工厂的时候,通常不用创建简单工厂类的类实例,可以把简单工厂类实现成一个工具类,直接使用静态方法,也就是说简单工厂用于创建实例的方法通常都是静态的,所以也被称为静态工厂模式,它是工厂方法模式的弱化,并不属于GoF 23种设计模式。
简单工厂结构
简单工厂模式的结构如下图所示:
在简单工厂模式结构图种包含以下几个角色:
- Factory(工厂角色):它是简单工厂模式的核心,负责实现创建所有Api实例的逻辑,工厂类可以被外部(客户端)直接调用,创建所需Api对象。
- Api(抽象Api角色):它是工厂类所创建的所有对象的接口,封装了用户api公有的方法。
- ApiImpl(具体Api角色):它是简单工厂模式的创建目标。每一个具体的Api都实现或者继承了抽象Api,需要实现在Api接口中声明的抽象方法。
对图中出现的角色简短的描述就是:
Api:定义客户所需要的功能接口
Impl:具体实现Api接口的实现类,可能会有多个
Factory:工厂类,通过传入的参数选择合适的实现类来创建Api接口对象
Client:客户端,通过Factory去获取Api接口对象,面向Api接口编程
示例代码
Api接口定义,定义了公共方法:
/**
* Api 接口定义
*/
public interface Api {
/**
* 具体的功能方法的定义,打印输出
*/
public void print(String s);
}
Api接口实现类:
/**
* Api接口具体实现类A
*/
public class ApiImplA implements Api {
@Override
public void print(String s) {
System.out.println(s + " 正在打印...");
}
}
/**
* Api接口具体实现类B
*/
public class ApiImplB implements Api {
@Override
public void print(String s) {
System.out.println(s + " 正在打印...");
}
}
典型的简单工厂代码如下所示:
/**
* 工厂类,用来生产Api的对象实例
*/
public class Factory {
public static Api produceApi(String arg) {
Api api = null;
if (arg.equals("A")) {
api = new ApiImplA();
} else if (arg.equals("B")) {
api = new ApiImplB();
}
return api;
}
}
客户端调用示例代码:
/**
* 客户端
*/
public class Client {
public static void main(String[] args) {
Api apiA = Factory.produceApi("A");
apiA.print("使用简单工厂创建的A对象");
Api apiB = Factory.produceApi("B");
apiB.print("使用简单工厂创建的B对象");
}
}
在java中,讲究面向接口编程,而接口的思想就是封装隔离,Api的接口实现类ImplA和ImplB,应该是被接口Api封装并同客户端隔离开的。所以客户端可以通过工厂类来创建具体的Api对象,而不再使用new关键字来创建对象。
注意到以上实现简单工厂模式有一个缺点:从客户端在调用工厂创建对象的时候,需要传入选择的参数,这就说明客户端必须知道每个参数的含义,也需要理解每个参数对应的功能处理。这就要求必须在一定程度上,向客户暴露了一定的内部实现细节。
可配置的简单工厂
在现有的Api实现中,每更换或者新增一种Api的接口实现,都需要修改客户端代码中静态工厂方法的参数,那么有没有一种方法能够在不修改客户端代码的前提下更换Api的实现?
一种常用的实现方式是将静态工厂方法的参数存储在properties或xml格式的配置文件中。
把参数存储在如下factory.xml中:
<?xml version="1.0" encoding="UTF-8"?>
<api>
<type>simple_factory.ApiImplA</type>
</api>
用一个工具类ConfigUtil读取factory.xml中配置的参数:
import java.io.File;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class ConfigUtil {
public static String getApiType() {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new File("src/simple_factory/factory.xml"));
NodeList nl = doc.getElementsByTagName("type");
Node node = nl.item(0).getFirstChild();
return node.getNodeValue().trim();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
引入ConfigUtil工具类后修改工厂类代码:
/**
* 工厂类,用来生产Api的对象实例
*/
public class Factory {
public static Api produceApi() {
Api api = null;
String arg = ConfigUtil.getApiType();
/**
* 通过反射创建对象
*/
try {
api = (Api)Class.forName(arg).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return api;
}
}
此时,客户端无需再传入参数:
/**
* 客户端
*/
public class Client {
public static void main(String[] args) {
Api apiA = Factory.produceApi();
apiA.print("使用简单工厂创建的对象");
}
}
如果需要更换具体的Api对象,只需修改factory.xml文件,不用修改工厂类以及客户端代码,符合“开闭原则”。
简单工厂模式本质
简单工厂模式提供工厂类选择具体的实现来创建对象,将对象的创建跟对象的使用分开,从而使得客户端和具体实现之间解耦,因此要是具体实现发生变化,就不再需要相应地去修改客户端了。
适用场景
- 需要完全封装隔离的具体实现,客户端只能通过接口来操作封装的内部逻辑,知道传入的参数,但并不关心具体对象的创建。
- 需要创建的对象较少,想把外部创建对象的职责集中管理和控制,这样工厂方法内部逻辑也不至于太过复杂。