定义一个用于创建对象的接口,让子类决定实例化哪一个类,Factory Method 使一个类的实例延迟到其子类。
工厂方法模式的解决思路: 那就是不解决,采取无为而治的方式;
工厂方法模式的结构如图:
Product: 定义工厂方法所创建的对象的接口,也就是实际需要使用的对象的接口。
ConcreteProduct: 具体Product 接口的实现对象
Creator: 创建器,声明工厂方法,工厂方法通常会返回一个Product类型的实例对象,而且多是抽象方法。也可以在Creator里面提供工厂方法的默认实现,让工厂方法返回一个缺省的Product类型的实例对象。
ConcreteCreator: 具体的创建器对象,覆盖实现Creator定义的工厂方法,返回具体的Product实例。
/**
* 工厂方法所创建的对象的接口
* @author Administrator
*
*/
public interface Product {
//可以定义Product的属性和方法
}
/**
* 具体的Product对象
* @author Administrator
*
*/
public class ConcreteProduct implements Product
{
}
/**
* 创建器,声明工厂方法
* @author Administrator
*
*/
public abstract class Creator {
/**
* 创建Product的工厂方法
* @return
*/
protected abstract Product factoryMethod();
/**
* 实现某些功能的方法
*/
public void someOperation() {
//通常在这些方法实现中需要调用工厂方法来获取Product产品
Product product = factoryMethod();
}
}
/**
* 具体的创建器实现对象
* @author Administrator
*
*/
public class ConcreteCreator extends Creator{
@Override
protected Product factoryMethod() {
// 重定义工厂方法,返回一个具体的Product对象
return new ConcreteProduct();
}
}
(1) Product 定义的示例代码如下:
(2)Product 实现对象的示例代码如下:
(3)创建器定义的示例代码如下:
(4) 创建器实现对象的示例代码如下:
考虑这样的一个实际应用:实现一个导出数据的应用框架,来让客户选择数据的导出方式;并真正执行数据的导出,通常这种系统在导出数据上会有一些约定的方式,比如导出成文本格式、数据库备份形式、Excel 格式、Xml格式等。
(1) 导出接口
(2) 实现导出PDF的示例代码:
(3) 实现导出Excel的示例代码:
(4) 实现ExportOperate 的示例代码如下:
(5) 加入了两个Creator实现
(6)客户端直接创建需要使用的Creator对象,然后调用相应的功能方法。示例代码如下:
/**
* 导出的文件对象的接口
* @author Administrator
*
*/
public interface ExportFileApi {
/**
* 导出内容成为文件
* @param data 需要保存的数据
* @return 导出是否成功
*/
public boolean export(String data);
}
/**
* 导出成文本文件格式的对象
* @author Administrator
*
*/
public class ExportExcelFile implements ExportFileApi{
@Override
public boolean export(String data) {
System.out.println("导出数据"+data+"到Excel文件中!!");
return true;
}
}
/**
* 导出成数据库备份文件形式的对象
* @author Administrator
*
*/
public class ExportPDFFile implements ExportFileApi{
@Override
public boolean export(String data) {
System.out.println("导出数据"+data+"到PDF文件!!");
return true;
}
}
/**
* 实现导出数据的业务功能对象
* @author Administrator
*
*/
public abstract class ExportOperate {
/**
* 导出文件
* @param data 需要保存的数据
* @return 是否成功导出文件
*/
public boolean export(String data) {
//使用工厂方法
ExportFileApi api = factoryMethod();
return api.export(data);
}
/**
* 工厂方法,创建导出的文件对象的接口对象
* @return
*/
protected abstract ExportFileApi factoryMethod();
}
/**
* 具体的创建器实现对象,实现创建导出成PDF格式的对象
* @author Administrator
*
*/
public class ExportPDFFileOperate extends ExportOperate
{
@Override
protected ExportFileApi factoryMethod() {
//创建导出成PDF格式的文件
return new ExportPDFFile();
}
}
/**
* 具体的创建器实现对象,实现创建导出成Excel格式的对象
* @author Administrator
*
*/
public class ExportExcelFileOperate extends ExportOperate
{
@Override
protected ExportFileApi factoryMethod() {
//创建导出成Excel格式的文件
return new ExportExcelFile();
}
}
public class Client {
public static void main(String[] args) {
//创建需要使用的Creator对象
ExportOperate operate = new ExportExcelFileOperate();
// 调用输出数据的功能方法
operate.export("测试数据");
}
}
所谓参数化工厂方法指的就是: 通过给工厂方法传递传递参数,然工厂方法根据参数的不同来创建不同的产品对象
使用参数化工厂方法实现导出文件功能:
/**
* 实现导出数据的业务功能对象
* @author Administrator
*
*/
public class ExportOperate {
/**
* 导出文件
* @param data 需要保存的数据
* @return 是否成功导出文件
*/
public boolean export(int type , String data) {
//使用工厂方法
ExportFileApi api = factoryMethod(type);
return api.export(data);
}
/**
* 工厂方法,创建导出的文件对象的接口对象
* @return
*/
protected ExportFileApi factoryMethod(int type) {
ExportFileApi api = null;
if(type == 1) {
api = new ExportPDFFile();
}else if(type == 2) {
api = new ExportExcelFile();
}
return api;
}
}
测试客户端:
public class Client {
public static void main(String[] args) {
//创建需要使用的Creator对象
ExportOperate operate = new ExportOperate();
// 调用输出数据的功能方法
operate.export(1,"测试数据");
}
}
使用参数化工厂方法,扩展起来会非常容易,已有的代码不会改变,只需要加入一个子类来提供新的工厂来提供新的工厂方法实现,然后在客户端使用这个新的子类即可。
扩展一个导出成xml文件的示例代码如下:
/**
* 导出成数据库备份文件形式的对象
* @author Administrator
*
*/
public class ExportXMLFile implements ExportFileApi{
@Override
public boolean export(String data) {
System.out.println("导出数据"+data+"到XML文件!!");
return true;
}
}
然后扩展ExportOperate 类,来加入新的实现。示例代码如下:
/**
* 实现导出数据的业务功能对象
* @author Administrator
*
*/
public class ExportOperate2 extends ExportOperate{
/**
* 导出文件
* @param data 需要保存的数据
* @return 是否成功导出文件
*/
public boolean export(int type , String data) {
//使用工厂方法
ExportFileApi api = factoryMethod(type);
return api.export(data);
}
/**
* 工厂方法,创建导出的文件对象的接口对象
* @return
*/
protected ExportFileApi factoryMethod(int type) {
ExportFileApi api = null;
if(type == 3) {
api = new ExportPDFFile();
}else {
api = super.factoryMethod(type);
}
return api;
}
}
工厂方法模式的优缺点
优点:
工厂方法模式可以让你在实现功能的时候,如果需要某个产品对象,只需要使用产品的接口即可,而无需关心具体的实现。选择具体实现的任务延迟到子类去完成。
工厂方法给子类提供了一个挂钩,使得扩展新的对象版本变得非常容易。比如,上面示例的参数化工厂方法实现中,扩展一个新的导出xml文件格式的实现,已有的代码都不会改变,只要新加入一个子类来提供新的工厂方法实现,然后在客户端使用这个新的子类即可。
缺点:
具体产品对象和工厂方法的耦合性
工厂方法模式很好地体现了—依赖倒置原则
依赖倒置原则告诉我们-要依赖抽象,不要依赖于具体类,简单点说就是: 不能让高层组件依赖于低层组件,而且不管高层组件还是低层组件,都应该依赖于抽象。
比如前面的示例,实现客户端请求操作的ExportOperator 就是高层组件;而具体实现数据导出的对象就是底层组件,比如ExportPDFFile、ExportExceFile;而ExportFileApi 接口就相当于是那个抽象。
对于ExportOperate 老说,它不关心具体的事项暗示,它只是"面向接口编程";对于具体的实现来说,它只关心自己“如何实现接口”所要求的功能。
那么倒置的是什么呢?倒置的是这个接口的"所有权"。事实上,ExportFileApi 接口中定义的功能,都是由高层组件ExportOperate来提出 的 要求,也就是说接口中的功能,是高层组件需要的功能。但是高层组件只是提出要求,并不关心如何实现,而底层组件,就是来真正实现高层组件所要求的接口功能的。因此看起来,底层实现的接口的所有权并不在底层组件手中,而是倒置到高层组件去了。
建议在 以下情况中现在用工厂方法模式。
如果一个类需要创建某个接口的对象,但是又不知道具体的实现,这种情况可以选用工厂方法模式,把创建对象的工作延迟到子类中去实现。
如果一个类本身就希望由它的子类来创建所需的对象的时候,应该使用工厂方法模式。