zoukankan      html  css  js  c++  java
  • 设计模式 之 工厂模式

    本文通过MetaWeblog自动发布,原文及更新链接:https://extendswind.top/posts/technical/design_patterns_factory

    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。感觉工厂方法模式虽然对加入新的产品能够降低修改,但反射同样可以解决此问题。

  • 相关阅读:
    Jenkins持续集成邮件发送
    基于appium快速实现H5自动化测试
    Linux常用命令学习一
    BZOJ4372烁烁的游戏——动态点分治+线段树(点分树套线段树)
    BZOJ3730震波——动态点分治+线段树(点分树套线段树)
    BZOJ1014[JSOI2008]火星人——非旋转treap+二分答案+hash
    BZOJ1299[LLH邀请赛]巧克力棒——Nim游戏+搜索
    BZOJ1115[POI2009]石子游戏——阶梯Nim游戏
    BZOJ3110[Zjoi2013]K大数查询——权值线段树套线段树
    BZOJ5343[Ctsc2018]混合果汁——主席树+二分答案
  • 原文地址:https://www.cnblogs.com/fly2wind/p/9993991.html
Copyright © 2011-2022 走看看