设计模式(四)工厂模式

作者: Arnold_J | 来源:发表于2017-12-12 10:42 被阅读0次

    我们在第一次学习对象这个概念的时候,新建对象用的是这样的代码:

    Product product = new Product();
    

    但是真正到了工程上这样的代码是“生硬”的,当遇到 Product 改变,或者 Product 初始化流程更改这样的新需求时,要改变的代码量相当的大。我们需要在找出所有调用 Product 构造器的地方,并为之添加上新的代码,这样的代码设计显然是不符合设计原则的。

    为了改善上面的情况,工厂模式应运而生。

    工厂模式是一种创建模式。创建模式对类的实例化过程进行了抽象,能将类的创建和使用分离。工厂类将产品类的初始化封装在方法之中,对于外界而言,只要知道产品类的公共接口,就可以正常实现功能,这使得各个模块开发时只专注于自己的业务逻辑,忽略对象的创建细节。

    由于场景不同,我们可以看到不同形态的工厂模式,一般可以分为以下几种:

      1. 简单工厂模式
      1. 工厂方法模式
      1. 抽象工厂模式

    三种模式间并没有绝对的优劣,因为各个模式各有优缺,需要根据特定的场景来选择。但是大多数开发场景中,我们并没有这样庞大的系统,因此,通常我们使用的还是前两种方法。

    一、简单工厂模式

    简单工厂模式是一种类创建模式,通常情况下,简单工厂的对象会有一个静态方法用于创建产品,并且这个方法通常根据传入的参数来生产指定的产品。这种工厂模式即使你不知道有设计模式这一说法,也会自行使用,因为它构造简单,实现方便,也确实能在项目中实现立竿见影的效果。

    1)简单工厂模型

    SimpleFactory.png

    先来看看最简单的实现代码:

    public class SimpleFactory {
        
        public static Product createProduct(String arg){
            Product product = null;
            switch (arg) {
                case "A":
                    product = new ProductA();
                    
                    // init something
                    
                    break;
                case "B":
                    product = new ProductB();
    
                    // init something
                    
                    break;
                default:
                    throw new RuntimeException("product not exist for arg: "+arg);
            }
            return product;
        }
        
    }
    

    2)使用场景
    对于产品种类少,且产品相对固定,出现新种类产品的可能性小的时候,可以使用简单工厂模式

    我们假定一个简单的场景,依旧用上一篇建造者模式中提到的电脑。

    现在电脑都可以办公和学习,因此,我们定义一个公共接口:

    public interface IComputer {
    
        void work();
    
        void study();
        
    }
    

    然后,现在一个人有一台电脑用来工作或者学习,但是这台电脑是有配置高低分别的,这时候我们需要两个类实现上面的接口,并且在构造器里,分别配置它的硬件信息:

    public class ComputerA implements IComputer {
        private static final String TAG = "ComputerA";
    
        public ComputerA(){
            //初始化高端硬件配置
            Log.i(TAG, "ComputerA: 初始化高端硬件配置");
        }
    
        @Override
        public void work() {
            //高端机工作
            Log.i(TAG, "ComputerA: 高端机工作");
        }
    
        @Override
        public void study() {
            //高端机玩学习
            Log.i(TAG, "ComputerA: 高端机玩学习");
        }
    }
    
    public class ComputerB implements IComputer {
        private static final String TAG = "ComputerB";
        
        public ComputerB(){
            //初始化普通硬件配置
            Log.i(TAG, "ComputerA: 初始化普通硬件配置");
        }
    
        @Override
        public void work() {
            //普通机工作
            Log.i(TAG, "ComputerA: 普通机工作");
        }
    
        @Override
        public void study() {
            //普通机玩学习
            Log.i(TAG, "ComputerA: 普通机玩学习");
        }
    }
    

    通过构造器,我们已经可以获取到不同硬件配置的电脑,然后我们需要对不同电脑安装适合的工作学习软件:

    public class SimpleFactory {
    
        public static IComputer createProduct(String arg){
            IComputer product = null;
            switch (arg) {
                case "A":
                    product = new ComputerA();
                    // 下载学习软件 M
                    // 下载工作软件 N
                    // init something
    
                    break;
                case "B":
                    product = new ComputerB();
                    // 下载学习软件 X
                    // 下载工作软件 Y
                    // init something
    
                    break;
                default:
                    throw new RuntimeException("product not exist for arg: "+arg);
            }
            return product;
        }
    
    }
    

    下面在想要工作学习的地方,我们只需要传入所需电脑对应的名称,就可以获取我们需要的电脑并且实现我们工作学习的目的:

    IComputer iComputerA = SimpleFactory.createProduct("A");
    iComputerA.study();
    iComputerA.work();
    
    IComputer iComputerB = SimpleFactory.createProduct("B");
    iComputerB.study();
    iComputerB.work();
    

    下面是这个过程的日志图:


    效果图-g.png

    对比代码和日志,我们可以看到,由于简单工厂的封装,在需要使用电脑工作学习的时候,我们只要知道当前需要的电脑名称:“A” 或 “B”,就可以获取我们的电脑,并且通过统一的接口方法实现工作学习的目的。

    这样做,显然使得代码变得更加简洁,对于业务而言逻辑也变得清楚,因为无关的东西都被隐藏了起来。

    3)优缺点
    **优点:
    构造容易、逻辑简单。只要通过一个工厂方法就可以得到需要的对象,不用关注这个对象是如何构造出来。工厂模式能略过了产品的初始化细节,并且规范统一了所有产品功能的调用方法。

    **缺点:
    由于简单工厂所有产品的初始化方法都在一个方法中,当产品数量过多且初始化过程复杂的时候,代码会显得过长且难以理解。同时,通过标志位分辨生产产品的类型,使得代码内部耦合严重,如果需要新添加一个产品 C ,就必须要修改这个工厂类对象,这显然不符合前面提到的 开放封闭原则(ASD) ,即对拓展开放,对修改封闭。优秀的设计应该能够在有新产品的时候,尽量保持原代码不变,只添加新代码就能实现功能。这一点,简单工厂模式无法达到。

    那么这一点,我们需要怎样达到呢?于是,工厂方法模式出现了。

    二、工厂方法模式

    工厂方法模式也是一种类创建模式,与简单工厂不同的是,它不再通过参数来区分当前生产的产品对象。工厂方法模式将工厂抽象化,定义了一个公共父类用于规定创建产品对象的公共接口,并把所有产品实力化的时间延迟到工厂的子类。文字说明有些不好理解,先来看一下图解。

    MethodFactory.png

    由图可以看到,对应产品 A 有指定的 FactoryA 用于生产,对应产品 B 有指定的 FactoryB 来生产。FactoryA 和 FactoryB 都是 IFactory 的子类,如果想要生产新的产品 C ,只需要多加一个 IFactory 子类用于生产 C ,而不需要改动原有代码。因此这种模式,也叫做多态工厂模式。

    还是用上面的电脑例子,现在我们要多加一个生产工厂,生产低端电脑:

    MethodFactory2.png

    工厂方法模式遵循[开放封闭原则(ASD)],但是它也有缺点,即完成相同的功能,对于工厂方法而言要新增一个工厂类一个产品类,文件数量多,使得系统变得复杂。

    而现在问题又来了,高端、中端、低端电脑都有了,但是实际上,对于电脑我们不只有配置高低,还有品牌。现在高端、中端、低端电脑提供商有两家,一家是联想,一家是宏基。在这样的情况下, 工厂方法模式的设计并没有办法让我们获取我们需要的品牌的电脑。于是,我们用到了抽象工厂模式。

    三、抽象工厂模式

    抽象工厂模式是对工厂模式的抽象。事实上,抽象工厂模式是最具有一般性质的工厂模式。为了了解这个最为抽象的模式,首先,我们需要了解几个概念,我们还是会以熟悉的电脑为例子进行说明:

    • **产品继承结构:例如高端电脑是一个类别,而联想高端电脑和宏基高端电脑,都是这个类别的子类,实现了它的功能接口。
    • **产品族:产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品。这里是指同个品牌下的一系列产品,例如联想有高端、中端、低端的电脑产生流水线,同样的宏基也有,而这高端、中端、低端的三种电脑,就是他们的产品族

    在这里,例子中的高端、中端和低端更换为商务本、游戏本和二合一的平板笔记本可能更合适些。不过,管他呢。

    一个抽象工厂模式一般包含如下角色:
    AbstractFactory:抽象工厂
    ConcreteFactory:具体工厂
    AbstractProduct:抽象产品
    Product:具体产品

    先看下图解:


    AbstractFactory.png

    可以看到,不同品牌的生产工厂分别实现了抽象工厂的接口,将自己的生产过程封装起来,在我们需要电脑工作学习的时候,只需要根据需要的品牌选取合适的工厂类,然后利用工厂类选择合适的机型,就可以获取我们需要的电脑对象:

    public interface IComputerFactory {
        IComputer createComputerA();
        IComputer createComputerB();
    }
    
    public class LenovoComputerFactory implements IComputerFactory {
        private static final String TAG = "LenovoComputerFactory";
    
        public LenovoComputerFactory(){
            Log.i(TAG, "LenovoComputerFactory: 联想电脑工厂初始化");
        }
        @Override
        public IComputer createComputerA() {
            Log.i(TAG, "createComputerA: 联想电脑工厂制造高级配置电脑");
            return new ComputerA();
        }
    
        @Override
        public IComputer createComputerB() {
            Log.i(TAG, "createComputerB: 联想电脑工厂制造普通配置电脑");
            return new ComputerB();
        }
    }
    

    调用日志打印:


    效果图-g.png

    抽象工厂模式的优点在于,新添加产品族是符合[开放封闭原则(ASD)],但是新增产品的继承结构却是不符合的,在具体项目中需要权衡倾向,再作应用。

    感谢:

    1. android_interview - builder_pattern
    2. UML类图与类的详解

    以上。

    相关文章

      网友评论

        本文标题:设计模式(四)工厂模式

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