美文网首页
10 【创建型模式】工厂三兄弟之工厂方法模式

10 【创建型模式】工厂三兄弟之工厂方法模式

作者: 猿笔记 | 来源:发表于2017-12-24 11:41 被阅读13次

定义

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

解决的问题

  • 简单工厂模式最大的缺点是当有新产品要加入到系统中时,必须修改工厂类,需要在其中加入必要的业务逻辑,这违背了“开闭原则”
  • 在简单工厂模式中,所有的产品都由同一个工厂创建,工厂类职责较重,业务逻辑较为复杂,具体产品与工厂类之间的耦合度高,严重影响了系统的灵活性和扩展性

结构图


要素:
● Product(抽象产品):定义产品的接口,是工厂方法模式所创建对象的超类型,即产品对象的公共父类。
● ConcreteProduct(具体产品):实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,具体工厂和具体产品之间一一对应。
● Factory(抽象工厂):在抽象工厂类中,声明了工厂方法(Factory Method),用于返回一个产品。抽象工厂是工厂方法模式的核心,所有创建对象的工厂类都必须实现该接口。
● ConcreteFactory(具体工厂):它是抽象工厂类的子类,实现了抽象工厂中定义的工厂方法,并可由客户端调用,返回一个具体产品类的实例。

interface Factory {  
    public Product factoryMethod();  
}  

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

……  
Factory factory;  
factory = new ConcreteFactory(); //可通过配置文件实现  
Product product;  
product = factory.factoryMethod();  
……  

示例

  Sunny软件公司欲开发一个系统运行日志记录器(Logger),该记录器可以通过多种途径保存系统的运行日志,如通过文件记录或数据库记录,用户可以通过修改配置文件灵活地更换日志记录方式。在设计各类日志记录器时,Sunny公司的开发人员发现需要对日志记录器进行一些初始化工作,初始化参数的设置过程较为复杂,而且某些参数的设置有严格的先后次序,否则可能会发生记录失败。如何封装记录器的初始化过程并保证多种记录器切换的灵活性是Sunny公司开发人员面临的一个难题。


//日志记录器工厂  
class LoggerFactory {  
    //静态工厂方法  
    public static Logger createLogger(String args) {  
        if(args.equalsIgnoreCase("db")) {  
            //连接数据库,代码省略  
            //创建数据库日志记录器对象  
            Logger logger = new DatabaseLogger();   
            //初始化数据库日志记录器,代码省略  
            return logger;  
        }  
        else if(args.equalsIgnoreCase("file")) {  
            //创建日志文件  
            //创建文件日志记录器对象  
            Logger logger = new FileLogger();   
            //初始化文件日志记录器,代码省略  
            return logger;            
        }  
        else {  
            return null;  
        }  
    }  
}  

存在的问题:
(1) 工厂类过于庞大,包含了大量的if…else…代码,导致维护和测试难度增大
(2) 系统扩展不灵活,如果增加新类型的日志记录器,必须修改静态工厂方法的业务逻辑,违反了“开闭原则”
解决方案:

//日志记录器接口:抽象产品  
interface Logger {  
    public void writeLog();  
}  
  
//数据库日志记录器:具体产品  
class DatabaseLogger implements Logger {  
    public void writeLog() {  
        System.out.println("数据库日志记录。");  
    }  
}  
  
//文件日志记录器:具体产品  
class FileLogger implements Logger {  
    public void writeLog() {  
        System.out.println("文件日志记录。");  
    }  
}  
  
//日志记录器工厂接口:抽象工厂  
interface LoggerFactory {  
    public Logger createLogger();  
}  
  
//数据库日志记录器工厂类:具体工厂  
class DatabaseLoggerFactory implements LoggerFactory {  
    public Logger createLogger() {  
            //连接数据库,代码省略  
            //创建数据库日志记录器对象  
            Logger logger = new DatabaseLogger();   
            //初始化数据库日志记录器,代码省略  
            return logger;  
    }     
}  
  
//文件日志记录器工厂类:具体工厂  
class FileLoggerFactory implements LoggerFactory {  
    public Logger createLogger() {  
            //创建文件日志记录器对象  
            Logger logger = new FileLogger();   
            //创建文件,代码省略  
            return logger;  
    }     
}  

class Client {  
    public static void main(String args[]) {  
        LoggerFactory factory;  
        Logger logger;  
        factory = new FileLoggerFactory(); //可引入配置文件实现  
        logger = factory.createLogger();  
        logger.writeLog();  
    }  
}  

进一步解决方案:通过配置文件读取具体的工厂类

//工具类XMLUtil.java  
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 {  
            //创建DOM文档对象  
            DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();  
            DocumentBuilder builder = dFactory.newDocumentBuilder();  
            Document doc;                             
            doc = builder.parse(new File("config.xml"));   
          
            //获取包含类名的文本节点  
            NodeList nl = doc.getElementsByTagName("className");  
            Node classNode=nl.item(0).getFirstChild();  
            String cName=classNode.getNodeValue();  
              
            //通过类名生成实例对象并将其返回  
            Class c=Class.forName(cName);  
            Object obj=c.newInstance();  
            return obj;  
        }     
        catch(Exception e) {  
            e.printStackTrace();  
            return null;  
         }  
    }  
}  

class Client {  
    public static void main(String args[]) {  
        LoggerFactory factory;  
        Logger logger;  
        factory = (LoggerFactory)XMLUtil.getBean(); //getBean()的返回类型为Object,需要进行强制类型转换  
        logger = factory.createLogger();  
        logger.writeLog();  
    }  
}  

需求进一步要求

  Sunny公司开发人员通过进一步分析,发现可以通过多种方式来初始化日志记录器,例如可以为各种日志记录器提供默认实现;还可以为数据库日志记录器提供数据库连接字符串,为文件日志记录器提供文件路径;也可以将参数封装在一个Object类型的对象中,通过Object对象将配置参数传入工厂类。此时,可以提供一组重载的工厂方法,以不同的方式对产品对象进行创建。当然,对于同一个具体工厂而言,无论使用哪个工厂方法,创建的产品类型均要相同。


interface LoggerFactory {  
    public Logger createLogger();  
    public Logger createLogger(String args);  
    public Logger createLogger(Object obj);  
}  

class DatabaseLoggerFactory implements LoggerFactory {  
    public Logger createLogger() {  
            //使用默认方式连接数据库,代码省略  
            Logger logger = new DatabaseLogger();   
            //初始化数据库日志记录器,代码省略  
            return logger;  
    }  
  
    public Logger createLogger(String args) {  
            //使用参数args作为连接字符串来连接数据库,代码省略  
            Logger logger = new DatabaseLogger();   
            //初始化数据库日志记录器,代码省略  
            return logger;  
    }     
  
    public Logger createLogger(Object obj) {  
            //使用封装在参数obj中的连接字符串来连接数据库,代码省略  
            Logger logger = new DatabaseLogger();   
            //使用封装在参数obj中的数据来初始化数据库日志记录器,代码省略  
            return logger;  
    }     
}  
  
//其他具体工厂类代码省略 

工厂方法的隐藏:直接调用业务方法

//改为抽象类  
abstract class LoggerFactory {  
    //在工厂类中直接调用日志记录器类的业务方法writeLog()  
    public void writeLog() {  
        Logger logger = this.createLogger();  
        logger.writeLog();  
    }  
      
    public abstract Logger createLogger();    
}  

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

总结

主要优点
(1) 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
(2) 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够让工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,就正是因为所有的具体工厂类都具有同一抽象父类。
(3) 使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了,这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。

主要缺点
(1) 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
(2) 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。

适用场景

(1) 客户端不知道它所需要的对象的类。在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建,可将具体工厂类的类名存储在配置文件或数据库中。
(2) 抽象工厂类通过其子类来指定创建哪个对象。在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏替换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。

相关文章

  • java设计模式-工厂模式之简单工厂

    工厂模式属于创建型设计模式(用于创建对象),今天这里要讲的就是工厂三兄弟之简单工厂。工厂三兄弟:(简单工厂,工厂,...

  • 设计模式-创建型模式-工厂模式(工厂三兄弟)

    设计模式-创建型模式-工厂模式(工厂三兄弟)工厂模式分为简单工厂,工厂方法,抽象工厂。 简单工厂模式 一个接口,三...

  • 设计模式(1) 工厂方法模式

    创建型模式 简单工厂模式 工厂方法模式IOC与工厂方法模式的结合泛型工厂委托工厂 创建型模式 创建型模式可以隔离客...

  • 简单工厂模式

    Android进阶之设计模式 简单工厂模式 简单工厂模式(又叫作静态工厂方法模式), 其属于创建型设计模式,但并不...

  • 2021-11-16 - 学习记录

    设计模式学习:创建型:工厂(工厂方法,抽象工厂),建造者,单例,原型 设计模式:工厂模式 简单工厂 工厂方法 抽象工厂

  • Kotlin(八)kotlin设计模式-创建型

    创建型模式 - 工厂 Kotlin里面几种主流创建型设计模式:工厂方法模式,抽象工厂模式,构建者模式 8.1 伴生...

  • 工厂方法模式

    什么是工厂方法模式 工厂方法模式又称为工厂模式,属于创建型设计模式。工厂方法模式定义了一个创建对象的接口,但由子类...

  • 工厂模式-简单工厂模式、工厂方法模式、抽象工厂模式

    工厂模式 工厂模式是一种比较常见的创建型设计模式,分为简单工厂模式、工厂方法模式、抽象工厂模式 工厂模式旨在把对象...

  • 简单工厂模式

    工厂方法模式(Factory Pattern)是创建型设计模式之一。 一、定义 简单工厂模式又称为静态方法工厂模式...

  • 设计模式[3]-抽象工厂模式-Abstract Factory

    1.抽象工厂模式介绍 抽象工厂模式是创建型设计模式,可以创建一组对象。与工厂方法模式相比,抽象工厂模式中的具体工厂...

网友评论

      本文标题:10 【创建型模式】工厂三兄弟之工厂方法模式

      本文链接:https://www.haomeiwen.com/subject/ksofgxtx.html