1 工厂方法模式的定义
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
1.1 工厂方法模式的通用类图
在工厂方法模式中,抽象产品类Product负责定义产品的共性,实现对事物最抽象的定义;Creator为抽象创建类,也就是抽象工厂,具体如何创建产品类是由具体的实现工厂ConcreteCreator完成的。
1.2 工厂方法模式的实用通用源码
(1) 抽象产品类
public abstract class Product { //产品类的公共方法
public void method1(){
//业务逻辑处理
}
//抽象方法
public abstract void method2();
}
(2) 具体产品类1、2
/**
* 具体的产品类可以有多个,都继承于抽象产品类
*/
public class ConcreteProduct1 extends Product {
public void method2() {
//业务逻辑处理
}
}
public class ConcreteProduct2 extends Product {
public void method2() {
//业务逻辑处理
}
}
(3) 抽象工厂类
/**
* 抽象工厂类负责定义产品对象的产生
*/
public abstract class Creator {
// 创建一个产品对象,其输入参数类型可以自行设置通常为String、Enum、Class等,当然也可以为空
public abstract <T extends Product> T createProduct(Class<T> c);
}
(4)具体工厂类
/**
* 具体如何产生一个产品的对象,是由具体的工厂类实现的
*/
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;
}
}
(5) 场景类
public class Client {
public static void main(String[] args) {
Creator creator = new ConcreteCreator();
Product product1 =creator.createProduct(ConcreteProduct1.class);
Product product2 =creator.createProduct(ConcreteProduct2.class);
}
}
(5) 总结
该通用代码是一个比较实用、易扩展的框架,读者可以根据实际项目需要进行扩展。
2 工厂方法模式的应用
2.1 工厂方法模式的优点
(1)良好的封装性,代码结构清晰。一个对象创建是有条件约束的,如一个调用者需要一个具体的产品对象,只要知道这个产品的类名(或约束字符串)就可以了,不用知道创建对象的过程,降低模块间的耦合。
(2)工厂方法模式的扩展性非常优秀。在增加产品类的情况下,只要适当地修改具体的工厂类或扩展一个工厂类,就可以完成“拥抱变化”。例如在我们的例子中,需要增加一个棕色人种,则只需要增加一个ConcreteProduct3类,工厂类不用任何修改就可完成系统扩展。
(3)屏蔽产品类。这一特点非常重要,调用者不需要关心产品类的实现是如何变化的,它只需要关心产品的接口,只要接口保持不变系统中的上层模块就不要发生变化。因为产品类的实例化工作是由工厂类负责的,一个产品对象具体由哪一个产品生成是由工厂类决定的。
在数据库开发中,大家应该能够深刻体会到工厂方法模式的好处:如果使用JDBC连接数据库,数据库从MySQL切换到Oracle,需要改动的地方就是切换一下驱动名称(前提条件是SQL语句是标准语句),其他的都不需要修改,这是工厂方法模式灵活性的一个直接案例。
(4)工厂方法模式是典型的解耦框架。高层模块值需要知道产品的抽象类,其他的实现类都不用关心,符合迪米特法则,我不需要的就不要去交流;也符合依赖倒置原则,只依赖产品类的抽象;当然也符合里氏替换原则,使用产品子类替换产品父类!
2.2 工厂方法模式的使用场景
(1)工厂方法模式是new一个对象的替代品。所以在所有需要生成对象的地方都可以使用,但是需要慎重地考虑是否要增加一个工厂类进行管理,增加代码的复杂度。
(2)需要灵活的、可扩展的框架时,可以考虑采用工厂方法模式。例如需要设计一个连接邮件服务器的框架,有三种网络协议可供选择:POP3、IMAP、HTTP,我们就可以把这三种连接方法作为产品类,定义一个接口如:IConnectMail,然后定义对邮件的操作方法,用不同的方法实现三个具体的产品类再定义一个工厂方法,按照不同的传入条件,选择不同的连接方式。如此设计可以做到完美的扩展,如某些邮件服务器提供了WebService接口,我们只要增加一个产品类就可以了。
(3)工厂方法模式可以用在异构项目中。例如通过WebService与一个非Java的项目交互,虽然WebService号称是可以做到异构系统的同构化,但是在实际的开发中,还是会碰到很多问题,如类型问题、WSDL文件的支持问题,等等。从WSDL中产生的对象都认为是一个产品,然后由一个具体的工厂类进行管理,减少与外围系统的耦合。
(4)使用在测试驱动开发的框架下。例如,测试一个类A,就需要把与类A有关联关系的类B也同时产生出来,我们可以使用工厂方法模式把类B虚拟出来,避免类A与类B的耦合。目前由于JMock和EasyMock的诞生,该使用场景已经弱化了,读者可以在遇到此种情况时直接考虑使用JMock或EasyMock。
3 工厂方法模式的扩展
3.1 简单工厂模式(静态工厂模式)
我们这样考虑一个问题:一个模块仅需要一个工厂类,没有必要把它产生出来,使用静态的方法就可以了,根据这一要求,我们把上例中的Creator修改一下:
public class Creator {
/**
* 简单工厂模式或者静态工厂模式
* @param clz 产品对象类类型
* @return 具体的产品对象
*/
public static <T extends Product> T createProduct(Class<T> clz) {
Product product = null;
try {
product = (Product) Class.forName(clz.getName()).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return (T)product;
}
}
相对于工厂模式,简单工厂模式或者静态工厂模式中Creator类仅有两个地方发生变化:①去掉继承抽象类,②并在createProduct前增加static关键字。工厂类发生变化,也同时引起了调用者的变化。
3.2 升级为多个工厂类
当我们在做一个比较复杂的项目时,经常会遇到初始化一个对象很耗费精力的情况,所有的产品类都放到一个工厂方法中进行初始化会使代码结构不清晰。例如,一个产品类有5个具体实现,每个实现类的初始化方法都不相同,如果写在一个工厂方法中,势必会导致该方法巨大无比,考虑到需要结构清晰,我们就为每个产品定义一个创造者,然后由调用者自己去选择与哪个工厂方法关联。我们以女娲造人为例,每个人种都有一个固定的八卦炉,分别造出黑色人种、白色人种、黄色人种。
每个人种(具体的产品类)都对应了一个创建者,每个创建者都独立负责创建对应的产品对象,非常符合单一职责原则,按照这种模式我们来看看代码变化。
(1) 多工厂模式的抽象工厂类
public abstract class AbstractHumanFactory {
public abstract Human createHuman();
}
注意:抽象方法中已经不再需要传递相关参数了,因为每一个具体的工厂都已经非常明确自己的职责:创建自己负责的产品类对象。
(2) 黑色人种的创建工厂实现
public class BlackHumanFactory extends AbstractHumanFactory {
public Human createHuman() {
return new BlackHuman();
}
}
(3) 黄色人种的创建类
public class YellowHumanFactory extends AbstractHumanFactory {
public Human createHuman() {
return new YellowHuman();
}
}
(4) 白色人种的创建类
public class whiteHumanFactory extends AbstractHumanFactory {
public Human createHuman() {
return new WhiteHuman();
}
}
三个具体的创建工厂都非常简单,但是如果一个系统比较复杂时工厂类也会相应地变复杂。
(5) 场景类NvWa
public class NvWa {
public static void main(String[] args) {
System.out.println("--造出的第一批人是白色人种--");
Human whiteHuman = (new WhiteHumanFactory()).createHuman(); whiteHuman.getColor();
whiteHuman.talk();
System.out.println("\n--造出的第二批人是黑色人种--");
Human blackHuman = (new BlackHumanFactory()).createHuman();
blackHuman.getColor();
blackHuman.talk();
System.out.println("\n--造出的第三批人是黄色人种--");
Human yellowHuman = (new YellowHumanFactory()).createHuman();
yellowHuman.getColor();
yellowHuman.talk();
}
}
3.3 替代单例模式
单例模式的核心要求就是在内存中只有一个对象,通过工厂方法模式也可以只在内存中生产一个对象。
非常简单的类图,Singleton定义了一个private的无参构造函数,目的是不允许通过new的方式创建一个对象。
(1) 单例类
public class Singleton {
//不允许通过new产生一个对象
private Singleton(){
}
public void doSomething(){
//业务处理
}
}
(2)负责生成单例的工厂类
/**
* Singleton保证不能通过正常的渠道建立一个对象,那SingletonFactory通过反射方式创建建立一个单例对象
*/
public class SingletonFactory {
private static Singleton singleton;
static{
try {
Class cl= Class.forName(Singleton.class.getName()); //获得无参构造
Constructor constructor=cl.getDeclaredConstructor(); //设置无参构造是可访问的
constructor.setAccessible(true); //产生一个实例对象
singleton =
(Singleton)constructor.newInstance();
} catch (Exception e) {
//异常处理
}
}
public static Singleton getSingleton(){
return singleton;
}
}
通过获得类构造器,然后设置访问权限生成一个对象,然后提供外部访问,保证内存中的对象唯一。
以上通过工厂方法模式创建了一个单例对象,该框架可以继续扩展,在一个项目中可以产生一个单例构造器,所有需要产生单例的类都遵循一定的规则(构造方法是private),然后通过扩展该框架,只要输入一个类型就可以获得唯一的一个实例。
3.4 延迟初始化
一个对象被消费完毕后,并不立刻释放,工厂类保持其初始状态,等待再次被使用。
ProductFactory负责产品类对象的创建工作,并且通过prMap变量产生一个缓存,对需要再次被重用的对象保留,Product和ConcreteProduct是一个示例代码,请参考前面的代码。
(1) 延迟加载的工厂类
public class ProductFactory {
private static final Map<String,Product> prMap = new HashMap();
public static synchronized Product createProduct(String type) throws Excep;
Product product =null; //如果Map中已经有这个对象
if(prMap.containsKey(type)){
product = prMap.get(type);
} else {
if(type.equals("Product1")) {
product = new ConcreteProduct1();
} else {
product = new ConcreteProduct2();
}
//同时把对象放到缓存容器中 prMap.put(type,product);
}
return product;
}
代码还比较简单,通过定义一个Map容器,容纳所有产生的对象,如果在Map容器中已经有的对象,则直接取出返回;如果没有,则根据需要的类型产生一个对象并放入到Map容器中,以方便下次调用。
延迟加载框架是可以扩展的,例如限制某一个产品类的最大实例化数量,可以通过判断Map中已有的对象数量来实现。例如JDBC连接数据库,都会要求设置一个MaxConnections最大连接数量,该数量就是内存中最大实例化的数量。
4 工厂方法模式实战
(1) 抽象产品类,IoHandler.java
public abstract class IoHandler {
public abstract void add(String id,String name);
public abstract void remove(String id,String name);
public abstract void update(String id,String name);
public abstract String query(String id);
}
(2) 具体产品类,DBHandler.java,FileHandler.java,XmlHandler.java
public class DBHandler extends IoHandler{
@Override
public void add(String id, String name) {
}
@Override
public void remove(String id, String name) {
}
@Override
public void update(String id, String name) {
}
@Override
public String query(String id) {
return "DBHandler";
}
}
public class FileHandler extends IoHandler {
@Override
public void add(String id, String name) {
}
@Override
public void remove(String id, String name) {
}
@Override
public void update(String id, String name) {
}
@Override
public String query(String id) {
return "FileHandler";
}
}
public class XmlHandler extends IoHandler {
@Override
public void add(String id, String name) {
}
@Override
public void remove(String id, String name) {
}
@Override
public void update(String id, String name) {
}
@Override
public String query(String id) {
return "SMBrother";
}
}
(3) 抽象工厂类,IOFactory.java
public abstract class Factory {
public abstract <T extends IoHandler> T getIOHandler(Class<T> clz);
}
(4)具体工厂类
public class IOFactory extends Factory {
public <T extends IoHandler> T getIOHandler(Class<T> clz) {
IoHandler handler = null;
try {
handler = (IoHandler)Class.forName(clz.getName()).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return (T) handler;
}
}
(5) 场景类
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView tvContent = (TextView) findViewById(R.id.tvContent);
// 获取查询普通文件数据
Button btnFile = (Button) findViewById(R.id.btnFile);
btnFile.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
IoHandler handler = IOFactory.getIOHandler(FileHandler.class);
tvContent.setText(handler.query("333333333"));
}
});
// 获取查询Xml文件数据
Button btnXml = (Button) findViewById(R.id.btnXml);
btnXml.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
IoHandler handler = IOFactory.getIOHandler(XmlHandler.class);
tvContent.setText(handler.query("333333333"));
}
});
// 获取查询数据库文件数据
Button btnDB = (Button) findViewById(R.id.btnDB);
btnDB.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
IoHandler handler = IOFactory.getIOHandler(DBHandler.class);
tvContent.setText(handler.query("333333333"));
}
});
}
}
(6) 结果演示
5 参考文章
《Android源码设计模式解析与实战》
《设计模式之禅》