工厂方法模式(Factory Method Pattern)

       工厂方法模式(Factory Method Pattern):定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。工厂方法模式又简称为工厂模式(Factory Pattern),又可称作虚拟构造器模式(Virtual Constructor Pattern)或多态工厂模式(Polymorphic Factory Pattern)。工厂方法模式是一种类创建型模式。

工厂方法模式结构

       简单工厂模式虽然简单,但存在一个很严重的问题。当系统中需要引入新产品时,由于静态工厂方法通过所传入参数的不同来创建不同的产品,这必定要修改工厂类的源代码,将违背“开闭原则”,如何实现增加新产品而不影响已有代码?工厂方法模式应运而生,本文将介绍第二种工厂模式——工厂方法模式。

       在简单工厂模式中只提供一个工厂类,该工厂类处于对产品类进行实例化的中心位置,它需要知道每一个产品对象的创建细节,并决定何时实例化哪一个产品类。简单工厂模式最大的缺点是当有新产品要加入到系统中时,必须修改工厂类,需要在其中加入必要的业务逻辑,这违背了“开闭原则”。此外,在简单工厂模式中,所有的产品都由同一个工厂创建,工厂类职责较重,业务逻辑较为复杂,具体产品与工厂类之间的耦合度高,严重影响了系统的灵活性和扩展性,而工厂方法模式则可以很好地解决这一问题。

       在工厂方法模式中,我们不再提供一个统一的工厂类来创建所有的产品对象,而是针对不同的产品提供不同的工厂,系统提供一个与产品等级结构对应的工厂等级结构

       工厂方法模式提供一个抽象工厂接口来声明抽象工厂方法,而由其子类来具体实现工厂方法,创建具体的产品对象。工厂方法模式结构如下图所示:


        在工厂方法模式结构图中包含如下几个角色:

       ● Product(抽象产品):它是定义产品的接口,是工厂方法模式所创建对象的超类型,也就是产品对象的公共父类。

       ● ConcreteProduct(具体产品):它实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,具体工厂和具体产品之间一一对应。

       ● Factory(抽象工厂):在抽象工厂类中,声明了工厂方法(Factory Method),用于返回一个产品。抽象工厂是工厂方法模式的核心,所有创建对象的工厂类都必须实现该接口。

       ● ConcreteFactory(具体工厂):它是抽象工厂类的子类,实现了抽象工厂中定义的工厂方法,并可由客户端调用,返回一个具体产品类的实例。


工厂方法模式实现

解决方案一 工厂方法模式的实现

       与简单工厂模式相比,工厂方法模式最重要的区别是引入了抽象工厂角色,抽象工厂可以是接口,也可以是抽象类或者具体类,其关键实现代码如下所示:

public abstract class Product {
    public void sameOperation();
}

public class ConcreteProduct extends Product {
    public void sameOperation() {

    }
}

// 在抽象工厂中声明了工厂方法但并未实现工厂方法,具体产品对象的创建由其子类负责
public abstract class Factory {
    public Product factoryMethod();
}

// 具体工厂类实现了工厂方法,不同的具体工厂可以创建不同的具体产品
// 具体工厂类在实现工厂方法时除了创建具体产品对象之外
// 还可以负责产品对象的初始化工作以及一些资源和环境配置工作,例如连接数据库、创建文件等
public class ConcreteFactory implements Factory{
    public Product factoryMethod() {
        Product product = new ConcreteProduct();
        return product;
    }
}

// 客户端针对抽象工厂编程,可在运行时再指定具体工厂类
public class Client {
    public static void main(String args[]) {
        Factory factory;
        factory = new ConcreteFactory();
        Product product;
        product = factory.factoryMethod();
        product.sameOperation();
    }
}
       可以通过配置文件来存储具体工厂类ConcreteFactory的类名,更换新的具体工厂时无须修改源代码,系统扩展更为方便。

解决方案二 带配置文件的工厂方法模式的实现 

       为了让解决方案一中的工厂方法模式具有更好的灵活性和可扩展性,现在对系统进行重构,使得可以在不修改任何客户端代码的基础上更换或增加新的日志记录方式。

       在客户端代码中将不再使用new关键字来创建工厂对象,而是将具体工厂类的类名存储在配置文件(如XML文件)中,通过读取配置文件获取类名字符串,再使用Java的反射机制,根据类名字符串生成对象。在整个实现过程中需要用到两个技术:Java反射机制与配置文件读取。软件系统的配置文件通常为XML文件,我们可以使用DOM (Document Object Model)、SAX (Simple API for XML)、StAX (Streaming API for XML)等技术来处理XML文件。

Java反射(Java Reflection)是指在程序运行时获取已知名称的类或已有对象的相关信息的一种机制,包括类的方法、属性、父类等信息,还包括实例的创建和实例类型的判断等。在反射中使用最多的类是Class,Class类的实例表示正在运行的Java应用程序中的类和接口,其forName(String className)方法可以返回与带有给定字符串名的类或接口相关联的Class对象,再通过Class对象的newInstance()方法创建此对象所表示的类的一个新实例,即通过一个类名字符串得到类的实例。

       首先创建了如下XML格式的配置文件config.xml用于存储具体日志记录器工厂类类名:

<!— config.xml -->
<?xml version="1.0"?>
<config>
    <className>ConcreteFactory</className>  
</config>
       然后再通过一个工具类XMLUtil来读取配置文件中的具体产品类名,返回具体产品类型的一个实例对象,然后调用工厂类的工厂方法,创建具体的产品类对象。其关键实现代码如下所示:

public abstract class Product {
    public void sameOperation();
}

public class ConcreteProduct extends Product {
    public void sameOperation() {

    }
}

// 在抽象工厂中声明了工厂方法但并未实现工厂方法,具体产品对象的创建由其子类负责
public abstract class Factory {
    public Product factoryMethod();
}

// 具体工厂类实现了工厂方法,不同的具体工厂可以创建不同的具体产品
// 具体工厂类在实现工厂方法时除了创建具体产品对象之外
// 还可以负责产品对象的初始化工作以及一些资源和环境配置工作,例如连接数据库、创建文件等
public class ConcreteFactory implements Factory{
    public Product factoryMethod() {
        Product product = new ConcreteProduct();
        return product;
    }
}

import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import java.io.*;

public class XMLUtil {
    // 该方法用于从XML配置文件中提取具体产品类名,并返回一个实例对象
    public static Object getBean() {
        try {
            // 创建文档对象
            DocumentBuilderFactory documentFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = documentFactory.newDocumentBuilder();
            Document doc;
            doc = builder.parse(new File("config.xml"));

            // 获取包含产品类名的文本节点
            NodeList nodeList = doc.getElementsByTagName("productType");
            Node classNode = nodeList.item(0).getFirstChild();
            String productType = classNode.getNodeValue();

            // 通过类名生成实例对象并将其返回
            Class className = Class.forName(productType);
            Object object = className.newInstance();
            return object;
        } catch(Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

public class Client {
    public static void main(String args[]) {
        Factory factory;
        factory = (ConcreteFactory)XMLUtil.getBean();
        Product product;
        product = factory.factoryMethod();
        product.sameOperation();
    }
}

       引入XMLUtil类和XML配置文件后,如果要增加新的产品类型,只需要执行如下几个步骤:

       (1) 新的产品类型需要继承抽象产品类型Product;

       (2) 对应增加一个新的具体产品工厂,继承抽象产品工厂ProductFactory,并实现其中的工厂方法factoryMethod(),设置好初始化参数和环境变量,返回具体产品对象;

       (3) 修改配置文件config.xml,将新增的具体产品工厂类的类名字符串替换原有工厂类类名字符串;

       (4) 编译新增的具体产品类和具体产品工厂类,运行客户端测试类即可使用新的产品类型,而原有类库代码无须做任何修改,完全符合“开闭原则”。


解决方案三 重载的工厂方法 

       在抽象工厂中定义多个重载的工厂方法,在具体工厂中实现了这些工厂方法,这些方法可以包含不同的业务逻辑,以满足对不同产品对象的需求。当然,对于同一个具体工厂而言,无论使用哪个工厂方法,创建的产品类型均要相同。其结构类图如下所示;

       引入重载方法后,抽象工厂Factory和具体工厂ConcreteProduct的代码修改如下:
public abstract class Factory {
    public Product factoryMethod();
    public Product factoryMethod(String arg);
    public Product factoryMethod(Object object);
}

public class ConcreteFactory implements Factory{
    public Product factoryMethod() {
        Product product = new ConcreteProduct();
        return product;
    }
    public Product factoryMethod(String arg) {
        Product product = new ConcreteProduct();
        return product;
    }
    public Product factoryMethod(Object object) {
        Product product = new ConcreteProduct();
        return product;
    }
}


解决方案四 工厂方法的隐藏 

       有时候,为了进一步简化客户端的使用,还可以对客户端隐藏工厂方法,此时,在工厂类中将直接调用产品类的业务方法,客户端无须调用工厂方法创建产品,直接通过工厂即可使用所创建的对象中的业务方法。

       如果对客户端隐藏工厂方法,工厂方法模式的结构类图将修改为如下所示:


        引入重载方法后,抽象工厂Factory和客户端类Client的代码修改如下:
public abstract class Product {
    public void sameOperation();
}

public class ConcreteProduct extends Product {
    public void sameOperation() {

    }
}

// 在工厂类中直接调用具体产品类的业务方法sameOperation()
public abstract class Factory {
    public abstract Product factoryMethod(); // 抽象方法
    public void sameOperation(){
        Product product = this.factoryMethod();
        product.sameOperation();
    }
}

public class ConcreteFactory implements Factory{
    public Product factoryMethod() {
        Product product = new ConcreteProduct();
        return product;
    }
}

public class Client {
    public static void main(String args[]) {
        Factory factory;
        factory = (Factory)XMLUtil.getBean();
        factory.sameOperation(); // 直接使用工厂对象来调用产品对象的业务方法
    }
}


适应场景

       在以下情况下可以考虑使用工厂方法模式:

       (1) 客户端不知道它所需要的对象的类。在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建,可将具体工厂类的类名存储在配置文件或数据库中。

       (2) 抽象工厂类通过其子类来指定创建哪个对象。在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。


效果

       工厂方法模式是简单工厂模式的延伸,它继承了简单工厂模式的优点,同时还弥补了简单工厂模式的不足。工厂方法模式是使用频率最高的设计模式之一,是很多开源框架和API类库的核心模式。

1. 主要优点

       工厂方法模式的主要优点如下:

       (1) 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。

       (2) 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够让工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,就正是因为所有的具体工厂类都具有同一抽象父类。

       (3) 使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了,这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。

2. 主要缺点

      工厂方法模式的主要缺点如下:

      (1) 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。

      (2) 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。


应用实例

设计问题:COS系统是一款网上订餐软件,在COS系统中,提供统计数据文档Document对象导出服务,导出文档类型有:html、pdf等,文档导出业务由DocCreator实现。如何解决该设计问题?

解决方案:现用工厂方法模式解决这个问题,在COS系统中,Document充当抽象产品类,具体产品类有:HtmlDoc、PdfDoc,DocCreator充当抽象工厂类,具体工厂类有:HtmlCreator、PdfCreator,其类结构图如下所示:


       关键代码实现如下所示;

// 在抽象产品类中声明了公共业务方法但并未实现,具体业务方法的实现由其子类负责
public abstract class Document {
    public void sameOperation();
}

public class HtmlDoc extends Document {
    public void sameOperation() {

    }
}

public class PdfDoc extends Document {
    public void sameOperation() {

    }
}

// 在抽象工厂中声明了工厂方法但并未实现工厂方法,具体产品对象的创建由其子类负责
public abstract class DocCreator {
    public Product factoryMethod();
}

public class HtmlCreator implements DocCreator{
    public Document factoryMethod() {
        Document htmlDoc = new HtmlDoc();
        return htmlDoc;
    }
}

public class PdfCreator implements DocCreator{
    public Document factoryMethod() {
        Document pdfDoc = new PdfDoc();
        return pdfDoc;
    }
}

public class Client {
    public static void main(String args[]) {
        DocCreator docCreator1;
        docCreator1 = new HtmlCreator();
        Document document1;
        document1 = docCreator1.factoryMethod();
        document1.sameOperation();

        DocCreator docCreator2;
        docCreator2 = new PdfCreator();
        Document document2;
        document2 = docCreator2.factoryMethod();
        document2.sameOperation();
    }
}



参考书籍:http://www.chinasa.info/

code download:https://github.com/JyNeo/Software-Design-Pattern/blob/master/DesignPattern_CoreCode/FactoryMethodPattern.java

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值