Java常用的设计模式:工厂方法

一、场景问题:

    在一个大型系统中,需要一个框架来实现数据导出的功能,导出的数据格式可能是文本格式、XML格式或者是Excel格式。不管用户选择什么样的导出格式,最后导出的都是一个文件,因此应该是有一个统一的导出文件对象接口。形式如下:

package com.chenxyt.java.practice;
/**
 * 导出的文件对象的接口
 * @author Crayon
 *
 */
public interface ExportFileApi {
	/**
	 * 导出内容成为文件
	 * @param data
	 * @return
	 */
	public boolean export(String data);
}

对于实现数据导出的业务功能对象,也就是对于不同的客户部门来说,它应该根据自己相应的需求来创建相应的Export实现对象,比如A部门只能导出XML格式,B部门只能导出Excel格式。因此特定的实现对象是与特定的需求一一对应的,而对于需求部门来说,他们并不知道应该创建哪一个ExportFileApi对象,也不知道如何创建。也就是说,对于实现导出数据的业务功能对象,它需要创建ExportFileApi的具体实例对象,但是它只知道ExportFileApi接口,而不知道其具体实现,那么应该怎么办呢?还有另一个场景问题,某工厂一直在生产一批A种类的衣服,突然有一天来了新的需求,要生产B种类的衣服,而该工厂目前的技术和原料只能生产A种类的衣服,假如在此工厂进行改造的话,成本太大。那么该怎么办呢?

二、解决方案

2.1使用工厂方法设计模式解决问题

    上述问题的一个合理的解决方案就是使用工厂方法设计模式。工厂方法设计模式:定义一个用于创建对象的接口,让子类决定去实例化哪一个类,使具体类的实例化延迟到了工厂类的子类里。它的实现思路就是因为工厂类不确定具体要执行创建哪个类实例,那么就不进行创建,定义成抽象方法,然后由继承它的子类去决定创建哪一个类的对象。具体到上述的场景问题上,第一个数据倒出的问题就是提供一个公共的接口用来定义数据导出实现的接口,然后再定义一个公共的导出对象,最后由他们的子类一一对应,另一个生产衣服的场景问题,在别处新开一个工厂B,用来生产B种类的衣服,对外只声明我是在我们的工厂生产衣服,具体由哪个工厂生产哪种类型的衣服根据客户的选择。

2.2工厂方法设计模式的结构

    

Product:是我们要实现的各种产品的基类,可以定义为抽象类也可以定义为接口。所有的产品创建都交由它的子类或实现类分别实现。

Factory:工厂方法接口,我们要将创建产品的功能交给子类,所以该工厂类定义为抽象类。

PChildA/B:各种产品的子类,最终要创建的实例。

PFactoryA/B:对应创建各种产品的工厂子类,创建实例的时机延迟交给到该子类。

2.3示例代码

    首先定义一个要由工厂方法创建的对象的接口,他们最终的目的都是生产文件或者制作衣服,因此有共同的方法operation

package com.chenxyt.java.practice;
public interface Product {
	public void operation();
}

    然后分别定义其子类,子类对应做自己的业务。

package com.chenxyt.java.practice;

public class ProductA implements Product{
	@Override
	public void operation(){
		System.out.println("operator A");
	}
}
package com.chenxyt.java.practice;

public class ProductB implements Product{
	@Override
	public void operation(){
		System.out.println("operator B");
	}
}

    然后定义工厂的抽象基类,该基类内部有一个用来创建实例的抽象方法,返回Product类型。

package com.chenxyt.java.practice;
public abstract class Factory{
	public abstract Product createClass();
}

    最后是两个用来创建产品的工厂子类。

package com.chenxyt.java.practice;
public class FactoryA extends Factory{
	@Override
	public Product createClass() {
		// TODO Auto-generated method stub
		return new ProductA();
	}
}
package com.chenxyt.java.practice;
public class FactoryB extends Factory{
	@Override
	public Product createClass() {
		// TODO Auto-generated method stub
		return new ProductB();
	}
}

客户端使用如下的调用方式:

package com.chenxyt.java.practice;
public class Client {
	public static void main(String[] args) {
		FactoryA fa = new FactoryA();
		fa.createClass().operation();
	}
}

如上示例代码,当工厂中要加工ProductC时,无需改变工厂类,只需要使用继承的方式新增子类做扩展即可。这也解决了简单工厂模式中存在的不符合“开放-关闭”原则以及无法使用继承结构的问题。

2.4使用工厂方法模式来实现示例

    首先要使用设计模式解决问题,就先将问题中的各个模块与模式中的模块进行类比。这里以文件导出为示例,示例中的ExportFileApi可以类比成工厂方法设计模式中的Product,就是我最终的目的是创建文件导出实例。而暴露给客户端的实现导出数据的业务功能对象就相当于Factory了。把它们所处的模块分开之后,就可以分别实现了。

实现示例程序的结构图:

    

下面看一下具体的代码实现:

首先是导出文件对象接口 ExportFileApi,与之前定义的相同:

package com.chenxyt.java.practice;
/**
 * 导出的文件对象的接口
 * @author Crayon
 *
 */
public interface ExportFileApi {
	/**
	 * 导出内容成为文件
	 * @param data
	 * @return
	 */
	public boolean export(String data);
}

然后是这个接口的两个实现,功能分别为导出到文件和导出到数据库,为了演示示例,具体的文件操作和DB操作使用print代替

package com.chenxyt.java.practice;

public class ExportTxtFile implements ExportFileApi {

	@Override
	public boolean export(String data) {
		// TODO Auto-generated method stub
		System.out.println("导出数据到文件");
		return true;
	}

}
package com.chenxyt.java.practice;

public class ExportDbFile implements ExportFileApi {

	@Override
	public boolean export(String data) {
		// TODO Auto-generated method stub
		System.out.println("导出数据到数据库");
		return true;
	}

}

接下来就是工厂类的抽象基类

package com.chenxyt.java.practice;

public abstract class ExportFactory {
	public boolean export(String data){
		ExportFileApi api = createClass();
		return api.export(data);
	}
	protected abstract ExportFileApi createClass();
}

然后加入两个用来创建对象实例的子类

package com.chenxyt.java.practice;

public class TxtFactory extends ExportFactory{

	@Override
	protected ExportFileApi createClass() {
		// TODO Auto-generated method stub
		return new ExportTxtFile();
	}

}
package com.chenxyt.java.practice;

public class DbFactory extends ExportFactory{

	@Override
	protected ExportFileApi createClass() {
		// TODO Auto-generated method stub
		return new ExportDbFile();
	}

}

客户端调用方式:

package com.chenxyt.java.practice;
public class Client {
	public static void main(String[] args) {
		DbFactory expdb = new DbFactory();
		expdb.export("xxx");
	}
}

三、模式讲解

3.1认识工厂方法模式

    工厂方法模式的主要功能是让父类在不知道具体实现的情况下完成功能的调用,而具体的实现延迟到子类中。这样在设计的过程中,就不需要考虑具体的实现,需要哪个对象,把它通过工厂方法返回就好了,在使用这些对象实现功能的时候还是通过接口调用的形式实现

    通常情况下,工厂方法实现的父类会定义成抽象类,里面包含创建所需对象的抽象方法,这些抽象方法就是工厂方法。父类中还可以定义一些公共方法,用来实现不管选择了哪种工厂方法实现都可以做的功能。比如示例中的export方法,用来导出,而不管使用哪个子类创建对象,export都是导出数据。

    也可以将工厂方法实现的父类定义成具体的类,这样在没有具体子类可以选择的时候,可以默认返回父类所定义的方法。

    上面的示例可以看到,Client通过工厂方法已经获取到了创建的对象,这个对象实际上是在工厂方法基类中的其它方法中创建的,创建完成之后交给客户端。客户端要么使用Factory对象,要么使用Factory创建之后的对象,应避免客户端直接使用工厂方法来自己创建对象。

3.2工厂方法与IOC/DI

    IOC:控制反转

    DI:依赖注入

    以上两个概念改变了传统的资源获取方式由主动出击获取变成被动等待注入的形式。即A资源需要C资源,传统的形式是A资源主动创建获取C对象进行使用。而IOC/DI的思想则是将C资源的创建过程交给了容器,A资源需要C资源的时候,反向将获取到的C资源注入到A资源中。将原来的主动控制变为被动接受,也就是控制反转。IOC/DI的这种主从换位的思想与工厂方法的原理类似,举例说明使用IOC进行setter注入的方式:

package com.chenxyt.java.practice;

public class A {
	/**
	 * 等待注入的资源C
	 */
	private C c = null;
	
	/**
	 * 注入资源C的方法
	 * @param c 被注入的资源
	 */
	public void setC(C c){
		this.c = c;
	}
	public void t1(){
		/**
		 * 这里需要资源C 但是又不让主动创建 
		 * 那就不需要管了 创建的过程已经交给容器了 直接使用即可
		 */
		c.tc();
	}
	
}
package com.chenxyt.java.practice;

public interface C {
	public void tc();
}

上面的示例中,在A中需要使用C的部分,只需要提供注入的途径即可,然后就正常操作使用。

接下来是工厂方法模式中,如何实现类似上边的思想。

package com.chenxyt.java.practice;
public abstract class ExportFactory {
	public boolean export(String data){
		/**
		 * 我需要api对象来执行导出操作 这里不能明确实例化 那就等着吧
		 * 直接用就好了
		 */
		ExportFileApi api = createClass();
		return api.export(data);
	}
	/**
	 * 类似setter方法的注入函数,提供了获取对象的注入路径
	 * @return
	 */
	protected abstract ExportFileApi createClass();
}

3.3平行的类层次结构

     所谓平行类层次结构,就算是在两个类层次中,其中一个类层次中的每一个类,在另一个类层次中都有一个对应的类。比如A公司分为总经理、项目经理、员工,B公司同样也这样划分,这两个就是平行的类层次结构。在工厂方法中,要创建的对象于工厂类就存在这种平行的类层次结构。每一个子类工厂方法类对应一个具体的要创建的接口实例。脑海中牢记上边的结构图即可,两个模块中的子类一一对应。

3.4参数化工厂方法

    参数化工厂方法就是通过给工厂方法传递参数,让工厂方法选择需要创建的对象,从而实现可配置化的功能。当然需要创建的不同对象必须是同一个类型的。同前边的示例相比,Product处没有变化,仅仅在ExportFactory做了修改,原来是定义为抽象类,然后由具体的实现去创建。现在修改一下

package com.chenxyt.java.practice;
public  class ExportFactory {
	
	/**
	 * 导出数据
	 * @param type 用户选择模式
	 * @param data 数据
	 * @return
	 */
	public boolean export(int type,String data){
		ExportFileApi api = createClass(type);
		return api.export(data);
	}
	protected ExportFileApi createClass(int type){
		ExportFileApi api = null;
		if(type == 1){
			api = new ExportTxtFile();
		}else if(type == 2){
			api = new ExportDbFile();
		}
		return api;
	}
	
}

客户端的调用形式

package com.chenxyt.java.practice;
public class Client {
	public static void main(String[] args) {
		ExportFactory ef = new ExportFactory();
		ef.export(1,"xxx");
	}
}

个人理解这种方式又抛弃了工厂方法的子类,而是使用if-else的形式来获取创建对象。参数化之后的扩展形式如下:

比如我们新增加了一个导出XML格式的扩展

package com.chenxyt.java.practice;

public class ExportXmlFile implements ExportFileApi{

	@Override
	public boolean export(String data) {
		// TODO Auto-generated method stub
		System.out.println("导出XML格式");
		return true;
	}

}

扩展方式如下,新增一个继承自ExportFactory的类,只选择实现ExportXml的形式,其它形式由父类完成,示例如下:

package com.chenxyt.java.practice;
public class ExportFactory2 extends ExportFactory{
	protected ExportFileApi createClass(int type){
		ExportFileApi api = null;
		if(type == 3){
			api =  new ExportXmlFile();
		}else{
			api = super.createClass(type);
		}
		return api;
	}

}

客户端的调用方式变为实现新创建的类的实例

package com.chenxyt.java.practice;
public class Client {
	public static void main(String[] args) {
		ExportFactory ef = new ExportFactory2();
		ef.export(3,"xxx");
	}
}

3.5工厂方法模式的优缺点

    优点:解决了简单工厂遗留的缺点问题,工厂方法是简单工厂的衍生版本,满足“开放-关闭”原则,同时也可以使用继承结构进行扩展。同时工厂方法符合面向接口编程的思想,可以在不知具体实现的情况下进行编程,创建实现对象的过程延迟到子类完成。

    缺点:具体的产品对象与工厂方法存在一定程度的耦合性,如前文所说的平行层次结构之间的一一对应关系。同时这种结构在扩展的过程中,也会使项目更加庞大,因为没增加一种产品,就要对应创建一个产品的子类和工厂子类。

3.6思考工厂方法模式

    工厂方法模式的本质是将创建对象的功能延迟到子类实现,而实际上它本身只是一种选择实现。这种思想就与简单工厂类似了,没错,在上文中的参数化工厂,如果单纯的看createClass这个工厂方法,那简直就是简单工厂的实现。本质上他们确实相似,因为都是选择性实现,区别在于工厂类使用的工厂方法可以依赖抽象而不是具体的实现。同时也可以延迟到子类完成对象的创建。

四、总结

    与简单工厂类似,工厂方法主要目的都是用来实现解耦,从而达到面向接口编程的目的。工厂方法的层次结构的核心在于每生产一种产品,对应了两个子类。一个是工厂的子类,一个是要创建对象的子类,二者一一对应。工厂方法的思想与IOC类似,都是从主动获取依赖对象转向为被动注入的形式。工厂方法的使用场景如果一个类想要创建某个接口的对象,但又不知道具体的实现,这种情况可以使用,或者一个类本身就希望由它的子类去创建对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值