再议工厂模式(Abstract Factory)和DIP的关系

大多数人说的工厂模式,应该是指GOF设计模式里面的Abstract Factory模式。

这是一种很常见又很有用的模式。它和DIP原则又有什么关系呢?

DIP原则

DIP: Dependency inversion principle。

DIP也就是依赖倒置原则,讲的是上层模块应该依赖于接口,具体类应该依赖于抽象接口(也就是被迫实现抽象接口)。因为抽象接口更接近于它的使用者(上层模块),所以看上去就像具体类依赖于上层模块一样,这才称之为依赖倒置。

如果严格按照DIP来讲,任何一条new语句就违反了DIP。比如:

class Pic
{
public:
     virtual void draw() = 0;
};

class Png : public Pic
{
public:
     virtual void draw()
     {
            printf("draw png\n");
     }
}

void main()
{
     Png* p = new Png();
     p->draw();
}

看上面的代码,main()函数作为Pic类库的使用者,new Png()本身就已经违反了DIP,这是不是很搞笑,这种代码很常见啊。但它实实在在违反了DIP。我们在客户代码里面看到了Png类,Png类是一个具体类,那就说明客户依赖于具体类了。那还不就是违反了DIP?严格来说,确实违反了。但是不一定有多大坏处。这个要看Png具体类发生变化的可能性有多大?如果Png类基本不会变,那违反就违反,没有什么关系。就好象我们使用stl里面的类一样,那些类几乎不会变,那客户代码直接依赖于它们,又有何不可。但是如果Png类发生变化的可能性很大,这个时候依赖于具体类就不是很好了。

上面的代码,如果改成:

void main()
{
     Pic* p = new Png();
     p->draw();
}

那么情况就会好很多,因为只有new依赖于具体类,new返回的指针保存在Pic* p里面,所有其他地方都将使用Pic*, 这就是说除了new本身,其他地方都是依赖于接口。但是new本身确实还是违法了DIP。我们可以画个图:

我们可以看到上面的图里面,main()作为一个客户,它强依赖(或者关联)于Pic接口,然后又有个Png的依赖关系(虚线)。确实,main()有很多地方需要调用Pic* p,所以是关联。在new Pic(),就是一个弱依赖关系。其实上面的这个设计在大多数情况下都是可行的,没什么问题,也很常见。

那么如果当Png变化的可能性很大的时候,比如Png的构造函数经常变,或者创建其他Pic子类的可能性很大,甚至Png的类名会变化。这个时候,上面的图就有点问题了。毕竟main弱依赖于具体类了。

这个时候,我们就可以考虑工厂模式了。(GOF叫做Abstract Factory)。

简单工厂模式(Abstract Factory)

我们可以考虑把上面的图演化成:

我们断开了main()和Png的依赖,取而代之的是引入一个工厂类PicFactory。main()关联了PicFactory,而PicFactory依赖于Png。代码大致如下:

class PicFactory
{
public:
      Pic* makePng()
      {
              return new Png()
      }
};

void main()
{
       PicFactory f;
       Pic* p = f.makePng();
} 

这就是一个简单工厂。这么做有什么好处呢?因为Png类很易变,所以我们把它放到了工厂类中。客户main()只需要使用工厂类来创建Pic对象。肯定有人会问,刚才我们是依赖于Png类,现在依赖于PicFactory类,这有什么分别呢?关键就在于:Png类或者Pic的其他子类易变,而工厂类比较稳定,依赖于一个比较稳定的类总比依赖于易变的类来的好。

比如我们现在需要增加一个新的Pic子类,叫做Gif,我们要做的就是:

class Gif : public Pic
{
public:
     virtual void draw()
     {
        printf("Gif::draw\n");
     }
};

class PicFactory
{
public:
         Pic* makePng()
         {
                return new Png();
         }
         Pic* makeGif()
         {
                return new Gif();
         }
}

void main()
{
          PicFactory f;
          Pic* gif = f.makeGif()
          gif->draw();
}

1. 新增一个Gif类

2. 在PicFactory里面增加一个函数makeGif()

3. 客户main那里就可以通过工厂来创建Gif对象了。

这么做有个问题,就是每次增加一个新的Pic子类,工厂都得相应增加一个函数,这也不是很舒服。

可以稍微变通一下,把工厂类改成:

typedef enum PicType{Png = 0, Gif};
class PicFactory
{
public:
    Pic* makePic(PicType t)
    {
           Pic* p = nullptr;
           switch(t)
           {
                case Png:
                     p = new Png();
                     break;
                case Gif:
                     p = new Gif();
                     break;
           }
           return p;
    }
}

这样的话,我们每次增加新的Pic子类,不需要增加新的工厂函数了,只需要修改一下makePic函数就行了。比原来的好一些。

工厂模式的另外一个好处就是:工厂本身也可以替换。

工厂模式(可替换)

考虑这么一个问题,Png有另外一种实现,这个Png支持在Edit Control里面画出来,比如在qq的编辑框里面画,我们叫做PngEx。这个时候有两种办法来修改工厂类:

1. 在makePic里面增加一个新的case,然后返回PngEx对象

2. 创建一个新的工厂类。

具体使用哪个,应该看具体情况。如果我们现在的需求是:更换一组对象的创建。那么可能#2会比较好。通常我们的一个工厂创建的是一组相关的产品,如果需要创建另外一组产品,那么就创建另外一个工厂。比如我们一个工厂创建一组Pic对象,它们只支持GDI绘画,另外一个工厂支持DIRECT X绘画。

要达到这种效果,我们首先需要改造工厂类,我们需要给工厂类弄一个抽象接口,实际上这就是Abstract Server模式。

看:

OK, 现在main()就依赖于工厂抽象类了,这就更加灵活了。当我们想使用另外一组产品的时候,换个工厂就行了。比如:

void main()
{
       PicFactory* f = new GDIFactory();
  //     PicFactory* f = new DXFactory();
       f->makePic();
}

直接更换工厂就可以创建另外一组产品。细心的同学一定会发现,这个new岂不是又违反了DIP?肯定是啊。是不是很晕?确实,很多时候设计领域总是会出现一些另外很纠结的事情。OK,之前也已经讲到过,如果严格遵守DIP的话,任何一行new代码都违反了DIP。关键是要看这个违反有没有关系。如果目标对象比较稳定,那么违反也不要紧。比如这里的Factory,除了它有可能增加新的派生工厂外,其他几乎不会变。所以这个地方的违反是不要紧的。我们之所以引入工厂,是因为Pic类很易变,工厂本身比较稳定,这个时候工厂模式就可以发挥作用了。

至于什么时候引入工厂模式,要看具体情况而定。一个比较简单的准则是:当你所要创建的产品(pic类)比较易变时,就该考虑了。

当然工厂模式也有一定的坏处:首先它要创建工厂类,这是一个主要开销。工厂模式的好处就是:可以解开客户和产品具体类的耦合,实现DIP原则。

实际上更加一般的情况是:一个工厂可以创建一系列产品,而这些产品并不是继承于同一个接口。更加一般的情况应该如下图:

迭代演变

工厂模式是一个很有效的模式,很多地方都可以看到。但是我们也不能一上来就使用,那就是滥用。因为工厂模式本身就会带来一些问题,最起码的引入了新的工厂类,这就是一个开销。没有最好的设计,只有合适的设计。当我们在某些场景需要考虑DIP原则的时候,工厂模式往往是很有效的。当然还有Abstract Server模式。他俩往往也是混合在一起的。通常我们在设计系统的时候应该是一个逐步迭代的过程,总是从最简单的设计慢慢一步步演变而来。驱动这种演变的往往是需求变动。当需求变动比较频繁的时候,我们才往复杂的结构演变。这个例子里面的迭代演变看起来就像是:

时间: 2024-11-26 05:30:25

再议工厂模式(Abstract Factory)和DIP的关系的相关文章

【设计模式】 抽象工厂模式 Abstract Factory Pattern

简单工厂模式是一个工厂类根据工厂方法的参数创建不出不同的产品, 工厂方法模式是每一个产品都有一个一一对应的工厂负责创建该产品.那么今天要讲的抽象工厂模式是一个工厂能够产生关联的一系列产品.抽象工厂模式相对于简单工厂和工厂方法模式来着更具抽象性. 一.抽象工厂模式演绎 我们先来看一个简单的需求: 甲方要开发一套办公自动化软件,其中有一个非常重要的功能就是要能够导入Word 文档和Excel 文档. 开发人员拿到需求后就开始编码了,  很快代码写完了: public class ImportTool

抽象工厂模式(Abstract Factory)C#实例

抽象工厂模式(Abstract Factory)C#实例 本文出处http://www.dofactory.com/net/abstract-factory-design-pattern 一.场景描述 本实例描述了抽象工厂模式的一个使用场景.在动物世界弱肉强食,食肉动物会吃掉食草动物.这是动物世界的规律,因此在动物世界类中有Runfoodchain(运行食物链)方法.在动物世界里总是有食肉动物和食草动物这两个抽象成员.它们之所以是抽象成员是因为他们不是具体的一种动物.而食草动物与食肉动物的区别在

二十四种设计模式:抽象工厂模式(Abstract Factory Pattern)

抽象工厂模式(Abstract Factory Pattern) 介绍提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类. 示例有Message和MessageModel,Message有一个Insert()方法,该方法的参数是MessageModel. AbstractMessageModel using System; using System.Collections.Generic; using System.Text; namespace Pattern.Abstract

设计模式 - 抽象工厂模式(abstract factory pattern) 详解

抽象工厂模式(abstract factory pattern) 详解 本文地址: http://blog.csdn.net/caroline_wendy/article/details/27091671 参考工厂模式: http://blog.csdn.net/caroline_wendy/article/details/27081511 抽象工厂模式: 提供一个接口, 用于创建相关或依赖对象的家族, 而不需要明确指定具体类. 全部代码: http://download.csdn.net/de

设计模式 - 抽象工厂模式(abstract factory pattern) 具体解释

抽象工厂模式(abstract factory pattern) 详细解释 本文地址: http://blog.csdn.net/caroline_wendy/article/details/27091671 參考工厂模式: http://blog.csdn.net/caroline_wendy/article/details/27081511 抽象工厂模式: 提供一个接口, 用于创建相关或依赖对象的家族, 而不须要明白指定详细类. 所有代码: http://download.csdn.net/

Android设计模式——抽象工厂模式(Abstract Factory)

二十三种设计模式分为三大类: 创建型模式,共五种:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式. 结构型模式,共七种:适配器模式.装饰器模式.代理模式.外观模式.桥接模式.组合模式.享元模式. 行为型模式,共十一种:策略模式.模板方法模式.观察者模式.迭代子模式.责任链模式.命令模式.备忘录模式.状态模式.访问者模式.中介者模式.解释器模式. 1 package com.example.main; 2 3 import android.app.Activity; 4 import

php设计模式——抽象工厂模式(Abstract Factory)

二十三种设计模式分为三大类: 创建型模式,共五种:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式. 结构型模式,共七种:适配器模式.装饰器模式.代理模式.外观模式.桥接模式.组合模式.享元模式. 行为型模式,共十一种:策略模式.模板方法模式.观察者模式.迭代子模式.责任链模式.命令模式.备忘录模式.状态模式.访问者模式.中介者模式.解释器模式. 1 <?php 2 /* 3 * php设计模式——抽象工厂模式(Abstract Factory) 4 */ 5 6 7 /* 8 * I

抽象工厂模式&lt;Abstract Factory&gt;

概述 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类.让子类决定实例化哪一个类 角色 抽象工厂(Creator):这个抽象类(或接口)声明一个创建对象的工厂方法,用来返回一个Product类型的对象. 具体工厂(ConcreteCreator):重定义工厂方法,返回一个具体的Concrete Product实例. 抽象产品(Product):定义工厂方法所创建的对象 具体产品(ConcreteProduct): 具体产品,继承自Product抽象类. 解读 UML图 c#代码

抽象工厂模式(Abstract Factory)

抽象工厂模式是对象的创建模式,他是工厂方法模式的进一步推广. 假设一个子系统需要一些产品对象,而这些产品又属于一个以上的产品等级结构.那么为了将消费这些产品对象的责任和创建这些产品对象的责任分割开来,可以引进抽象工厂模式.这样的话,消费一方不需要直接参与产品的创建工作,而只需要向一个公用的工厂接口请求所需要的产品. 抽象工厂模式的结构: 抽象工厂(AbstractFactory)角色:担任这个角色的是工厂方法模式的核心,它是与应用系统的商业逻辑无关的.通常使用java接口或者抽象java类实现,