zoukankan      html  css  js  c++  java
  • 【设计模式

    工厂模式(Factory Pattern)是最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

    在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

    其介绍如下:

    • 意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
    • 主要解决:主要解决接口选择的问题。
    • 何时使用:我们明确地计划不同条件下创建不同实例时。
    • 如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。
    • 关键代码:创建过程在其子类执行。
    • 应用实例: 您需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现。
    • 优点: 1、一个调用者想创建一个对象,只要知道其名称就可以了。 2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。 3、屏蔽产品的具体实现,调用者只关心产品的接口。
    • 缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。
    • 使用场景: 1、日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。 2、数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。 3、设计一个连接服务器的框架,需要三个协议,"POP3"、"IMAP"、"HTTP",可以把这三个作为产品类,共同实现一个接口。
    • 注意事项:作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。

    一、简单工厂模式

    举个例子,直接 new 对象的方式相当于当我们需要一个苹果时,我们需要知道苹果的构造方法,要一个梨子时,需要知道梨子的构造方法。更好的实现方式是有一个水果工厂,我们告诉工厂需要什么种类的水果,水果工厂将我们需要的水果制造出来给我们就可以了。这样我们就无需知道苹果、梨子是怎么种出来的,只用和水果工厂打交道即可。

    步骤 1

    创建基类 - 水果产品类:

    class Fruit
    {
    public:
        Fruit();
        float getPrice(); // 创建一个接口:获取水果价格
    };
    

    步骤 2

    创建派生类 - 苹果产品类:

    class Apple : public Fruit
    {
    public:
        Apple();
        // Override
        float getPrice() {
            return 8.0;
        }
    };
    

    创建派生类 - 梨子产品类:

    class Pear : public Fruit
    {
    public:
        Pear();
        // Override
        float getPrice() {
            return 5.0;
        }
    };
    

    步骤 3

    创建一个工厂,生成基于给定类型信息的实体水果产品类的对象。

    class FruitFactory
    {
    public:
        FruitFactory();
        Fruit *crate(string typeName) {
            if(typeName == "Apple")
                return new Apple();
            else if (typeName == "Pear")
                return new Pear();
            else
                printf("暂时没有这种水果");
    
            return  nullptr;
        }
    };
    

    步骤 4

    调用者:使用该工厂,通过传递类型信息来获取实体类的对象。

    int main(int argc, char *argv[])
    {
        // 创建水果工厂对象
        FruitFactory *fruitFactory = new FruitFactory();
        // 使用水果工厂,创建苹果产品对象
        Apple *pApple = (Apple *)fruitFactory->crate("Apple");
        printf("Apple: %f
    ", pApple->getPrice());
        // 使用水果工厂,创建梨子产品对象
        Pear *pPear = (Pear *)fruitFactory->crate("Pear");
        printf("Pear: %f
    ", pPear->getPrice());
    }
    

    输出如下:

    Apple: 8.000000
    Pear: 5.000000
    

    另外一个优点

    事实上,将构建过程封装的好处不仅可以降低耦合,如果某个产品构造方法相当复杂,使用工厂模式可以大大减少代码重复。比如,如果生产一个苹果需要苹果种子、阳光、水分,将工厂修改如下:

    class FruitFactory
    {
    public:
        FruitFactory();
        Fruit *crate(string typeName) {
            if(typeName == "Apple") {
                Appleseed appleseed = new Appleseed();
                Sunlight sunlight = new Sunlight();
                Water water = new Water();
                return new Apple(appleseed, sunlight, water);
            }
            else if (typeName == "Pear")
                return new Pear();
            else
                printf("暂时没有这种水果");
    
            return  nullptr;
        }
    };
    

    这样 Apple 类的代码完全不需要变化,而且调用者不需要在每次需要苹果时,自己先去构建苹果种子、阳光、水分以获得苹果。假如某天科家发明了让苹果更香甜的肥料,要加入苹果的生产过程中的话,也只需要在工厂中修改,不需要修改 Apple 类。

    不知不觉中,我们就写出了简单工厂模式的代码。工厂模式共有三种:

    • 简单工厂模式
    • 工厂方法模式
    • 抽象工厂模式

    注:在 GoF 所著的《议计模式》一书中,简单工厂模式被划分为工厂方法模式的一种特例,没有单独被列出来。


    总而言之,简单工厂模式就是让一个工厂类承担构建所有对象的职责。调用者需要什么产品,让工厂生产出来即可。它的弊端也显而易见:

    • 一是如果需要生产的产品过多,此模式会导致工厂类过于庞大,承担过多的职责,变成超级类。当苹果生产过程需要修改时,要来修改此工厂。梨子生产过程需要修改时,也要来修改此工厂。也就是说这个类不止一个引起修改的原因。违背了单一职责原则。
    • 二是当要生产新的产品时,必须在工厂类中添加新的分支。而开闭原则告诉我们:类应该对修改封闭。我们希望在添加新功能时,只需增加新的类,而不是修改既有的类,所以这就违背了开闭原则。

    二、工厂方法模式

    为了解决简单工厂模式的这两个弊端,工厂方法模式应运而生,它规定每个产品都有一个专属工厂。比如苹果有专属的苹果工厂,梨子有专属的梨子工厂,C++ 代码如下:

    苹果工厂:

    class AppleFactory
    {
    public:
        AppleFactory();
        // Override
        Fruit *crate() {
            return new Apple();
        }
    };
    

    梨子工厂:

    class PearFactory
    {
    public:
        PearFactory();
        // Override
        Fruit *crate() {
            return new Pear();
        }
    };
    
    

    调用者:

    int main(int argc, char *argv[])
    {
        // 创建苹果工厂对象
        AppleFactory *appleFactory = new AppleFactory();
        // 创建苹果对象
        Apple *pApple = (Apple *)appleFactory->crate();
        printf("Apple: %f
    ", pApple->getPrice());
    
        // 创建梨子工厂对象
        PearFactory *pearFactory = new PearFactory();
        // 创建梨子对象
        Pear *pPear = (Pear *)pearFactory->crate();
        printf("Pear: %f
    ", pPear->getPrice());
    }
    
    

    有读者可能会开喷了,这样和直接 new 出苹果和梨子有什么区别?上文说工厂是为了减少类与类之间的耦合,让调用者尽可能少的和其他类打交道。用简单工厂模式,我们只需要知道 FruitFactory,无需知道 Apple、Pear类,很容易看出耦合度降低了。但用工厂方法模式,调用者虽然不需要和 Apple、Pear类打交道了,但却需要和 AppleFactory、 PearFactory 类打交道。有几种水果就需要知道几个工厂类,耦合度完全没有下降啊,甚至还增加了代码量!

    这位读者请先放下手中的大刀,仔细想想,工厂模式的第二个优点在工厂方法模式中还是存在的。当构建过程相当复杂时,工厂将构建过程封装起来,调用者可以很方便的直接使用,同样以苹果生产为例:

    class AppleFactory
    {
    public:
        AppleFactory();
        Fruit *crate() {
            Appleseed appleseed = new Appleseed();
            Sunlight sunlight = new Sunlight();
            Water water = new Water();
            return new Apple(appleseed, sunlight, water);
        }
    };
    
    

    调用者无需知道苹果的生产细节,当生产过程需要修改时也无需更改调用端。同时,工厂方法模式解决了简单工厂模式的两个弊端。

    • 当生产的产品种类越来越多时,工厂类不会变成超级类。工厂类会越来越多,保持灵活。不会越来越大、变得臃肿。如果苹果的生产过程需要修改时,只需修改苹果工厂。梨子的生产过程需要修改时,只需修改梨子工厂。符合单一职责原则。
    • 当需要生产新的产品时,无需更改既有的工厂,只需要添加新的工厂即可。保持了面向对象的可扩展性,符合开闭原则。

    三、抽象工厂模式

    工厂方法模式可以进一步优化,提取出工厂接口。

    class IFactory
    {
    public:
        IFactory();
        virtual Fruit *create() = 0;
    };
    
    

    然后苹果工厂和梨子工厂都实现此接口:

    class AppleFactory : public IFactory
    {
    public:
        AppleFactory();
        // Override
        Fruit *create() {
            return new Apple();
        }
    };
    
    
    class PearFactory : public IFactory
    {
    public:
        PearFactory();
        // Override
        Fruit *create() {
            return new Pear();
        }
    };
    
    

    此时,调用者可以将 AppleFactory 和 PearFactory 统一作为 Factory 对象使用,调用者 C++ 代码如下:

    // 抽象工厂模式
    int main(int argc, char *argv[])
    {
        // 苹果工厂
        IFactory *pAppleFactory = new AppleFactory();
        Fruit *pApple = pAppleFactory->create();
        printf("Apple: %f
    ", pApple->getPrice());
    
        // 梨子对象
        IFactory *pPearFactory = new PearFactory();
        Fruit *pPear = pPearFactory->create();
        printf("Pear: %f
    ", pPear->getPrice());
    }
    
    

    输出如下:

    Apple: 8.000000
    Pear: 5.000000
    
    

    可以看到,我们在创建时指定了具体的工厂类后,在使用时就无需再关心是哪个工厂类,只需要将此工厂当作抽象的 IFactory 接口使用即可。这种经过抽象的工厂方法模式被称作抽象工厂模式。

    由于客户端只和 IFactory 打交道了,调用的是接口中的方法,使用时根本不需要知道是在哪个具体工厂中实现的这些方法,这就使得替换工厂变得非常容易。

    例如:

    int main(int argc, char *argv[])
    {
        IFactory *pFactory = new AppleFactory();
        Fruit *pFruit = pFactory->create();
        printf("Fruit: %f
    ", pFruit->getPrice());
    }
    
    

    如果需要替换为获取梨子价格,只需要更改一行代码即可:

    int main(int argc, char *argv[])
    {
        IFactory *pFactory = new PearFactory();
        Fruit *pFruit = pFactory->create();
        printf("Fruit: %f
    ", pFruit->getPrice());
    }
    
    

    总结

    IFactory 中只有一个抽象方法时,或许还看不出抽象工厂模式的威力。实际上抽象工厂模式主要用于替换一系列方法。例如将程序中的 SQL Server 数据库整个替换为 Access 数据库,使用抽象方法模式的话,只需在 IFactory 接口中定义好增删改查四个方法,让 SQLFactory 和 AccessFactory 实现此接口,调用时直接使用 IFactory 中的抽象方法即可,调用者无需知道使用的什么数据库,我们就可以非常方便的整个替换程序的数据库,并且让客户端亳不知情。

    抽象工厂模式很好的发挥了开闭原则、依赖倒置原则,但缺点是抽象工厂模式太重了,如果 IFactory 接口需要新增功能,则会影响到所有的具体工厂类。使用抽象工厂模式,替换具体工厂时只需更改一行代码,但要新増抽象方法则需要修改所有的具体工厂类。所以抽象工厂模式适用于增加同类工厂这样的横向扩展需求,不适合新增功能这样的纵向扩展。

    介绍抽象工厂模式好的还有另一篇文章:设计模式之抽象工厂模式(C++)


    参考

    知乎 - 如何学习设计模式? 热门回答

    菜鸟教程 - 设计模式篇


  • 相关阅读:
    sublime配置攻略
    Objective-C代码块语法(block)使用
    [C/C++]_[VS2010来源与UTF8中国字符串转码ANSI问题]
    Android资源管理框架(Asset Manager)简介和学习计划
    ExtJs自学教程(1):从一切API开始
    ORACLE触发特定的解释
    同TextView在不同的显示内容
    :gAudit
    IIS日志分析 MapReduce
    图片存储心得
  • 原文地址:https://www.cnblogs.com/linuxAndMcu/p/14348714.html
Copyright © 2011-2022 走看看