Skip to Content

设计模式 之 工厂模式

Table of Contents

TODO 某些思想感觉没写清楚和有重复

几种工厂模式(Factory Pattern)简介

工厂模式主要分为:

  • 简单工厂模式(Simple Factory Pattern)
  • 工厂方法模式(Factory Method Pattern 经常简称为工厂模式)
  • 抽象工厂模式(Abstract Factory Pattern)

主要思想:将类的创建逻辑转移到工厂类中,工厂类直接得到初始化后的产品类,使产品类的初始化逻辑清晰、一致,容易添加新的产品。

目标:

  • 将产品的创建逻辑(如读取本地文件、连接数据库)放入工厂类,简化使用逻辑。
  • 隐藏具体创建的对象,提高代码的通用性 (网上博客很多地方没提这点,只有结合java反射机制才行)

需求示例

简单工厂模式 和 工厂方法模式

  • 实现多个日志记录器logger(文件logger,数据库logger等)
  • 通过配置文件确定使用的具体logger类
  • 添加新的logger类不修改源码(添加新的java包并修改配置文件)

抽象工厂模式

抽象工厂模式应用场景略有不同。

存在多种不同的主题,每个主题都有不同的Button和Text的实现逻辑,因此每个主题都有Button和Text控件的派生类,导致类的初始化较多。

容易添加新的主题

不应用工厂模式的一般实现 (FactoryProblem.java)

  • logger 基类实现通用的日志记录功能,子类实现各自的特有功能
  • 使用时根据配置文件中的类型,new相应的子类

类的实现:

abstract class Logger {
   public void writeLog(){
       System.out.println("writeLog by Logger");
   }
   // 可添加公共实现
}

class FileLogger extends Logger {
    @Override
    public void writeLog(){
        System.out.println("writeLog by the FileLogger");
    }
}

class DataBaseLogger extends Logger {
    @Override
    public void writeLog(){
        System.out.println("writeLog by the DataBaseLogger");
    }
}

使用时:

if (loggerType.equals("database")){
    // 此处一般会添加相应的初始化
    logger = new FileLogger();
}
else if(loggerType.equals("file")){
    // 此处一般会添加相应的初始化
    logger = new DataBaseLogger();
}
else
    logger = null;

从一般实现到工厂方法模式

一般实现存在下面的两个问题。

问题一:根据字符串生成对象会产生大量的判断

简单工厂模式将对象的初始化放入工厂类中,以简化调用类的逻辑。(还可以使用后面的反射机制)

// simple factoryPatten
class SimpleFact {
    public static Logger produceLogger(){
        String loggerType;// 从配置文件中读取 也可放在函数的参数中
        Logger logger;
        if (loggerType == "file"){
            // 此处一般会添加相应的初始化(连接数据库等)
            logger = new FileLogger();
        }
        else if(loggerType == "database"){
            // 此处一般会添加相应的初始化 (创建日志文件等)
            logger = new DataBaseLogger();
        }
        else
            logger = null;
        return logger;
    }
}

使用时直接通过工厂类得到对象

Logger logger = SimpleFact.produceLogger();

实质上主要提高了代码的可读性,将logger的具体类型和初始化过程用单独的简单工厂类处理,主要为逻辑清晰上的优点。

仍存在添加新的对象需要修改简单工厂类的问题。

问题二:添加新的对象需要修改源码的问题

利用java的反射机制,直接通过字符串直接创建对象(一般loggerName来自配置文件)

// simple factoryPatten
class SimpleFact {
    public static Logger produceLogger2(String loggerName) {
         Logger logger = null;
         Class c = Class.forName(loggerName);
         logger = (Logger)c.newInstance();
         // 省略 try catch  ......
         return logger;
    }
}

添加新类时,直接添加新类的jar包,将类名添加到配置文件即可。

无法解决不同类的初始化逻辑不同的问题。

问题三:不同logger的初始化需要各自不同的设置

前面的反射使客户端无法对各个具体的logger派生类实现不同的初始化。当初始化过程复杂时,放在另一个类(工厂类)中会让逻辑更为清晰。(工厂模式)

而且通过工厂类的封装后有了相同的初始化逻辑,能够直接上用上面的反射创建工厂

对每个logger构建一个工厂类,使用工厂类初始化logger后得到最后的对象。

abstract class Logger {
   public void writeLog(){
       System.out.println("writeLog by Logger");
   }
   // 可添加公共实现
}
abstract class SuperFactory{
    public abstract Logger produceLogger();
}

class FileLogger extends Logger {
    @Override
    public void writeLog(){
        System.out.println("writeLog by the FileLogger");
    }
}
class FileLoggerFactory extends SuperFactory{
    @Override
    public Logger produceLogger(){
        // 初始化忽略
        return new FileLogger();
    }
}

class DataBaseLogger extends Logger {
    @Override
    public void writeLog(){
        System.out.println("writeLog by the DataBaseLogger");
    }
}
class DataBaseLoggerFactory extends SuperFactory{
    @Override
    public Logger produceLogger(){
        // 初始化忽略
        return new DataBaseLogger();
    }
}

添加新的产品时添加logger和对应的工厂类,然后通过配置文件创建对应的工厂即可。

ps: 感觉此处如果初始化较为简单,构建工厂类的行为有点多余。

再到抽象工厂模式

工厂方法模式的思想主要是每个产品类使用一个工厂类初始化。

类似提供多套主题的情况,每套主题有多个控件,此时可以使用一个工厂初始化同一主题下的多个产品对象。

// ---- button
class ButtonUI{
    public void print() { System.out.println("ButtonUI");}
}
class ButtonUI_theme1 extends ButtonUI{
    @Override
    public void print() { System.out.println("ButtonUI_theme1");}
}
class ButtonUI_theme2 extends ButtonUI{
    @Override
    public void print() { System.out.println("ButtonUI_theme2");}
}

// ---- text
class TextUI{
    public void print() { System.out.println("TextUI");}
}
class TextUI_theme1 extends TextUI{
    @Override
    public void print() { System.out.println("TextUI_theme1");}
}
class TextUI_theme2 extends TextUI{
    @Override
    public void print() { System.out.println("TextUI_theme2");}
}

// ---- factory
abstract class AbstractThemeFactory{
    public void printCommon(){
        System.out.println("common factory method");
    }
    abstract public ButtonUI createButton();
    abstract public TextUI createText();
}
class Theme1Factory extends AbstractThemeFactory{
    @Override
    public ButtonUI createButton(){ return new ButtonUI_theme1(); }

    @Override
    public TextUI createText() {
        return new TextUI_theme1();
    }
}
class Theme2Factory extends AbstractThemeFactory{

    @Override
    public ButtonUI createButton() {
        return new ButtonUI_theme2();
    }
    @Override
    public TextUI createText() {
        return new TextUI_theme2();
    }
}

// ------------ Test
public class AbstractFactory {

    public static void main(String args[]){
        AbstractThemeFactory factory = new Theme1Factory();
        ButtonUI buttonUI = factory.createButton();
        TextUI textUI = factory.createText();
    }
}

个人看法

new具体对象的主要问题不只是使用不同的类,而是不同类有不同的初始化流程需要处理。

个人认为工厂模式的主要作用为:

  • 将new具体的对象的过程放入工厂类解耦(需要和反射机制结合)
  • 处理多个产品的选择逻辑(通过if或者反射机制)
  • 处理产品的初始化逻辑
  • 对产品分类创建(抽象工厂)

工厂模式不应该被过分整体套用,而应对于具体解决的问题选择其中的处理方式。如对于随便几个参数就能初始化的情况,工厂类起到的作用并不大。

主要的启示:

  • 在有多个产品或以后可能有多个产品扩展的情况下(而且数量不会过多、初始化逻辑不复杂),使用简单工厂模式将产品的选择逻辑放在工厂类,出现产品变化时只需要修改工厂类而不需要修改每一处的使用逻辑。
  • 在产品类有较为复杂的初始化和其他逻辑时,使用工厂方法模式构建工厂类包装简化使用。
  • 在产品类较多且有明显的分类时,使用抽象工厂模式对每个分类的产品构建一个工厂类。

吐槽

很多人的工厂方法模式,将new具体的对象变成了new具体的工厂,然后工厂类就变成了一对一的产品初始化类,正常人都能想到的吧…

某些书上讲的各种问题感觉就是根据已有的模式写法强套的问题,如很多提到简单工厂模式的缺点在于不适合管理多个产品,添加新产品需要修改源码。前者只在于产品类需要复杂的初始化逻辑,后者和工厂方法模式一样使用反射就能解决了。

网上其他的看法

使用new对类实例化可能破坏类的可扩展性,由于new跟随的是具体的对象,很可能会被修改,因此给其他人使用的类要尽可能少用new。感觉工厂方法模式虽然对加入新的产品能够降低修改,但反射同样可以解决此问题。