百度工程师教你玩转设计模式(工厂模式)

3,116 阅读8分钟

图片

作者 | 北极星小组

想要写好代码,设计模式(Design Pattern)是必不可少的基本功,设计模式是对面向对象设计(Object Oriented Design)中反复出现的问题的解决方案,本篇介绍工厂模式(Factory Pattern)。

工厂模式属于创建型模式(Builder Pattern),提供了创建对象的最佳方式,在创建对象时,不会对客户端暴露对象的创建逻辑,而是通过使用共同的接口来创建对象。工厂模式应用的典型场景是,希望能够创建一个对象,但创建过程比较复杂,希望对外隐藏这些细节,比如:创建对象可能是一个pool里的,不是每次都凭空创建一个新的;对象创建时会有很多参数来决定如何创建出这个对象;创建一个对象有复杂的依赖关系。

实现方式上,主要有简单工厂模式(Simple Factory Pattern)、工厂方法模式(Factory Method Pattern)、抽象工厂模式(Abstract Factory Pattern)。

一、 简单工厂模式在文档解析场景中的应用

在日常开发场景中,如果要创建的产品(被创建的对象类)不多,只要一个工厂类就可以完成,这种模式叫“简单工厂模式”。使用简单工厂模式的客户端(具体调用方)只需要传入需要创建产品类的参数,不需要关心如何创建对象的逻辑,可以很方便地创建所需产品。

以Word2007类型文档解析场景为例:文档主体在document.xml文件,解析时根据内容结构维度分别创建Paragraph、Table、Draw等具体解析类。在解析流程中,如果直接构造对应解析类的对象使用,则会导致两者的耦合过重,可以使用**“简单工厂模式”**将解析类的实际创建工作推迟到工厂类中。这也满足创建型模式中所要求的“创建与使用相分离”的特点。

具体实现上包括以下几部分:

  • 简单工厂(SimpleFactory):是简单工厂模式的核心,这里负责实现创建所有具体Parser实例的内部逻辑。

  • 抽象产品(Product):是简单工厂创建的所有对象的父类,这里负责描述所有Parser实例共有的公共接口。

  • 具体产品(ConcreteProduct):是简单工厂模式的创建目标,这里负责创建具体的解析类。

public class DocxPaser {    
    //抽象产品:所有Parser共有的公共接口
    public interface IPaser {
        void process(string entity);
    }
    //具体产品:Paragraph Parser
    static class Paragraph implements IPaser {
        public void process(string entity) {
            System.out.println("解析 Paragraph...");
        }
    }
    //具体产品:Table Parser
    static class Table implements IPaser {
        public void process(string entity) {
            System.out.println("解析 Table...");
        }
    }
     //具体产品:Draw Parser
    static class Draw implements IPaser {
        public void process(string entity) {
            System.out.println("解析 Draw...");
        }
    }
    final class Const {
        static final int ENTITY_PARAGRAPHP = 0;
        static final int ENTITY_TABLE      = 1;
        static final int ENTITY_DRAW       = 2;
    }
    
    //简单工厂:负责实现创建所有具体Parser实例的内部逻辑
    static class ParserFactory {
        public static IPaser creatParser(int kind) {
            switch (kind) {
                case Const.ENTITY_PARAGRAPHP:
                    return new Paragraph();
                case Const.ENTITY_TABLE:
                    return new Table();
                case Const.ENTITY_DRAW:
                    return new Draw();
            }
            return null;
        }
    }
    
    // 简单使用示例
    public static void main(String[] args) {
        //entity 对应document.xml 此处略去具体获取过程
        ...

        //解析paragraph
        ParserFactory.creatParser(Const.ENTITY_PARAGRAPHP).process(entity)
        //解析table
        ParserFactory.creatParser(Const.ENTITY_TABLE).process(entity)
        //解析draw
        ParserFactory.creatParser(Const.ENTITY_DRAW).process(entity)
         
        ...
    }
}

二、工厂方法模式在自动化测试场景的应用

在简单工厂模式中,调用方只需要传入需要创建的对象类参数,就可以获得一个需要的对象,而不用关心这个对象的具体创建细节。在需要创建的对象类型较少,而且后续不再轻易增加的情况下简单工厂模式即可满足要求。但是针对需要创建的对象种类繁多,而且后续变更较为频繁的场景下,简单工厂模式有两个明显的问题:

  • 由于后续每次新增一种对象都需要修改工厂类中的判断逻辑,违反代码设计中的开闭原则

  • 由于需要对输入的参数进行判断然后初始化合适的类,简单工厂类中 if-else 或者 switch-case分支过多,代码显得较为臃肿

为了解决以上问题,可以选择工厂方法模式:定义一个创建对象的接口,然后实现不同对象的创建子类,对象的初始化交由各个工厂的子类去完成。他和简单工厂的区别点在于:

  • 开闭原则:新增对象的情况下,简单工厂模式下需要修改工厂类,而工厂方法模式只需要新增一个工厂的子类去完成新增对象的初始化即可。

  • 代码简洁:简单工厂通过参数选择并初始化子类,所有工作都由工厂类完成,工厂方法只是定义了一个创建对象的接口,对象的创建放在子类去实现。

以前端浏览器自动化测试的场景为例,在不同的浏览器场景下必须通过对应的浏览器驱动才能完成测试的执行,例如常见的Chrome 、Firefox、Safari、IE浏览器。这个场景就可以通过工厂方法模式来进行实现。首先是定义场景下的驱动对象,Driver为各个具体驱动需要继承的抽象类,其他的XDriver为具体某个浏览器的驱动对象。接下来定义对应的抽象工厂并实现不同对象初始化的工厂子类,AbstractFactory为抽象工厂,定义了创建驱动对象的接口,其他的每个工厂实现了抽象工厂的接口,并提供对应驱动的初始化能力。最后实际的使用者根据需要选择某个工厂类即可完成对应的驱动初始化工作。

public class FactoryDemo {

    //抽象对象:所有驱动对象的公共父类
    abstract static class Driver {
        abstract void process();
    }

    // 具体驱动对象:Chrome浏览器驱动对象
    static class ChromeDriver extends Driver {
        @Override
        void process() {
            System.out.println("ChromeDriver process"); // do something
        }
    }

    // 具体驱动对象:Firefox浏览器驱动对象
    static class FirefoxDriver extends Driver {
        @Override
        void process() {
            System.out.println("FirefoxDriver process"); // do something
        }
    }

    // 具体驱动对象:Safari浏览器驱动对象
    static class SafariDriver extends Driver {
        @Override
        void process() {
            System.out.println("SafariDriver process"); // do something
        }
    }

    // 抽象工厂 所有工厂的公共接口
    public interface AbstractFactory {
        Driver create();
    }

    // 具体工厂:负责Chrome浏览器驱动对象的创建
    static class ChromeDriverFactory implements AbstractFactory {
        @Override
        public Driver create() {
            return new ChromeDriver();
        }
    }

    // 具体工厂:负责Firefox浏览器驱动对象的创建
    static class FirefoxDriverFactory implements AbstractFactory {
        @Override
        public Driver create() {
            return new FirefoxDriver();
        }
    }

    // 具体工厂:负责Safari浏览器驱动对象的创建
    static class SafariDriverFactory implements AbstractFactory {
        @Override
        public Driver create() {
            return new SafariDriver();
        }
    }

    public static void main(String[] args) {
        // 创建 Chrome 驱动对象
        AbstractFactory chromeDriverFactory = new ChromeDriverFactory();
        Driver chromeDriver = chromeDriverFactory.create();
        chromeDriver.process();

        // 创建 Firefox 驱动对象
        AbstractFactory firefoxDriverFactory = new FirefoxDriverFactory();
        Driver firefoxDriver = firefoxDriverFactory.create();
        firefoxDriver.process();

        // ...
    }
}

三、抽象工厂模式在跨端场景下的应用

抽象工厂模式,是对工厂方法模式的进一步深化,工厂方法模式中只有一个抽象方法,要想实现多种不同的类对象,只能去创建不同的具体工厂方法的子类来实列化,而抽象工厂则是让一个工厂负责创建多个不同类型的对象。

以典型的跨端场景为例,用户可购买会员有多种类型,每种类型的会员在iOS、Android、PC下权益页稍有不同,并且实例化过程十分复杂。如果不用工厂模式,就需要在每处需要实例的地方,去编写复杂的实例化代码,当实例化过程发生修改,也需要每处修改,维护成本非常高。如果用简单工厂模式或者工厂方法模式,不用抽象工厂模式,就需要每个类型会员实现三个工厂,也不易于维护。

在这个场景下,我们可以用抽象工厂模式解决,编写抽象工厂、会员抽象类,并实现iOS、Android、PC三个具体工厂,根据场景选取需要的具体工厂获取会员实例。

//抽象产品
public interface Vip {}

//具体产品
public class NormalVip implements Vip {} //普通会员
public class MonthlyVip implements Vip {} //包月会员

public class IOSNormalVip extends NormalVip {}
public class AndroidNormalVip extends NormalVip {}
public class PCNormalVip extends NormalVip {}

public class IOSMonthlyVip extends MonthlyVip {}
public class AndroidMonthlyVip extends MonthlyVip {}
public class PCMonthlyVip extends MonthlyVip {}

//抽象工厂
public interface AbstractVipFactory {
    Vip createNormalVip();
    Vip createMonthlyVip();
}

//具体工厂
public class IOSVipFactory implements AbstractVipFactory {
    @Override
    public Vip createNormalVip() {
        return new IOSNormalVip();
    }
    
    @Override
    public Vip createMonthlyVip() {
        return new IOSMonthlyVip();
    }
}
...

//调用示例
public class Client {
    public static void main(String[] args) {
        IOSVipFactory iosVipFactory = new IOSVipFactory();
        Vip iosNormalVip = iosVipFactory.createNormalVip();
    }
}

四、总结

通过对以上三个实际案例的讲解和具体的代码实现阅读,大家对工厂模式的应用场景和具体实现方案应该有了更加深入的了解了。结合以上三个案例的的分析,创建过程复杂时,适合使用工厂模式,常用的工厂模式有:

  • 简单工厂模式:实例化过程复杂时,可以选用

  • 工厂方法模式:产品结构较复杂时,可以选用

  • 抽象工厂模式:产品种类结构多时,可以选用

---------- END ----------

推荐阅读【技术加油站】系列:

揭秘百度智能测试在测试分析领域实践

百度用户产品流批一体的实时数仓实践

ffplay视频播放原理分析

百度工程师眼中的云原生可观测性追踪技术

使用百度开发者工具 4.0 搭建专属的小程序 IDE

百度工程师教你玩转设计模式(观察者模式)