Loading...
墨滴

千羽的编程时光

2021/10/21  阅读:39  主题:嫩青

03-简单工厂模式

代码:https://github.com/nateshao/design-demo/tree/main/JavaDesignPatterns/factorymethod

1. 工厂方法模式概述

  • 使用简单工厂模式设计的按钮工厂

  • 使用工厂方法模式改进后的按钮工厂

工厂方法模式:

  • 不再提供一个按钮工厂类来统一负责所有产品的创建,而是将具体按钮的创建过程交给专门的工厂子类去完成
  • 如果出现新的按钮类型,只需要为这种新类型的按钮定义一个具体的工厂类就可以创建该新按钮的实例

工厂方法模式的定义

工厂方法模式:定义一个用于创建对象的接口,但是让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类

  • 简称为工厂模式(Factory Pattern)

  • 又可称作虚拟构造器模式(Virtual Constructor Pattern)或多态工厂模式(Polymorphic Factory Pattern)

  • 工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象

  • 目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类

2. 工厂方法模式的结构

工厂方法模式包含以下4个角色:

  1. Product(抽象产品)
  2. ConcreteProduct(具体产品)
  3. Factory(抽象工厂)
  4. ConcreteFactory(具体工厂)

工厂方法模式的实现

典型的抽象工厂类代码:

public interface Factory {
    public Product factoryMethod();
}

典型的具体工厂类代码:

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

典型的客户端代码片段:

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

3. 工厂方法模式的应用实例

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

为了更好地封装记录器的初始化过程并保证多种记录器切换的灵活性,现使用工厂方法模式设计该系统。

  • 实例类图

    日志记录器结构图
    日志记录器结构图
  1. Logger:日志记录器接口,充当抽象产品角色
  2. DatabaseLogger:数据库日志记录器,充当具体产品角色
  3. FileLogger:文件日志记录器,充当具体产品角色
  4. LoggerFactory:日志记录器工厂接口,充当抽象工厂角色
  5. DatabaseLoggerFactory:数据库日志记录器工厂类,充当具体工厂角色
  6. FileLoggerFactory:文件日志记录器工厂类,充当具体工厂角色
  7. Client:客户端测试类

结果及分析:在未使用配置文件和反射机制之前,更换具体工厂类需修改客户端源代码,但无须修改类库代码

4. 反射机制与配置文件

Java反射机制(Java Reflection)

  • Java反射(Java Reflection)是指在程序运行时获取已知名称的类或已有对象的相关信息的一种机制,包括类的方法、属性、父类等信息,还包括实例的创建和实例类型的判断等

  • Class类的实例表示正在运行的Java应用程序中的类和接口,其forName(String className)方法可以返回与带有给定字符串名的类或接口相关联的 Class对象,再通过Class对象的newInstance()方法创建此对象所表示的类的一个新实例,即通过一个类名字符串得到类的实例

//通过类名生成实例对象并将其返回
Class c = Class.forName(“java.lang.String");
Object obj = c.newInstance();
return obj;

配置文件

  • 纯文本文件,例如XML文件,properties文件……等

  • 通常是XML文件,可以将类名存储在配置文件中,例如具体工厂类的类名

<!— config.xml -->
<?xml version="1.0"?>
<config>
    <className>designpatterns.factorymethod.FileLoggerFactory</className>
</config>

XMLUtil.java

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("src//designpatterns//factorymethod//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;
        }
    }
}

Client.java

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

增加新产品的步骤

  1. 增加一个新的具体产品类作为抽象产品类的子类
  2. 增加一个新的具体工厂类作为抽象工厂类的子类,该工厂用于创建新增的具体产品对象
  3. 修改配置文件,用新的具体工厂类的类名字符串替换原有工厂类类名字符串
  4. 编译新增具体产品类和具体工厂类,运行客户端代码,即可完成新产品的增加和使用

5.工厂方法的重载

结构图

抽象工厂类LoggerFactory示意代码

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

具体工厂类DatabaseLoggerFactory示意代码:

public 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;
 } 
}
//其他具体工厂类代码省略

6. 工厂方法的隐藏

目的:为了进一步简化客户端的使用

实现:在工厂类中直接调用产品类的业务方法,客户端无须调用工厂方法创建产品对象,直接使用工厂对象即可调用所创建的产品对象中的业务方法

抽象工厂类LoggerFactory示意代码:

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

客户端代码

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

7. 工厂方法模式的优缺点与适用环境

模式优点

  1. 工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节
  2. 能够让工厂自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部
  3. 在系统中加入新产品时,完全符合开闭原则

模式缺点

  1. 系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,会给系统带来一些额外的开销
  2. 增加了系统的抽象性和理解难度

模式适用环境

  1. 客户端不知道它所需要的对象的类(客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体产品对象由具体工厂类创建)
  2. 抽象工厂类通过其子类来指定创建哪个对象

千羽的编程时光

2021/10/21  阅读:39  主题:嫩青

作者介绍

千羽的编程时光