简单工厂模式、工厂模式、抽象工厂模式的对比与应用场景(代码举例)

简单工厂模式、工厂模式以及抽象工厂模式都是非常常用的设计模式,这段时间在读《大话设计模式》,对于这几个的模式有了自己的认识,下面就通过几个例子,一步一步整理这3个模式的具体好处和应用场景。

首先模拟一个场景,假设有一个采购员,需要给公司采购键盘,购买键盘时,连接线长度需要自己指定:

class Keyboard
{
public:
    Keyboard (int wireLenth) { this.wireLenth = wireLenth; }
private:
    int wireLenth;
};

则购买键盘的代码如下:

 Keyboard* k1 = new Keyboard(1); // 1米连接线 

然后假设这个人买了好几个这种键盘,但是连接线长度各不相同,分为短线键盘和长线键盘:

Keyboard* k1 = new Keyboard(1);  // 1米连接线
Keyboard* k2 = new Keyboard(1);  // 1米连接线

Keyboard* k3 = new Keyboard(5);  // 5米连接线
Keyboard* k4 = new Keyboard(5);  // 5米连接线

那么问题来了,由于需求变更,我们需要将短线键盘改成2米,长线键盘改成7米(确实有点长。。。),按照上面的设计,我们需要在逐行修改每个键盘的创建代码,假如这个人采购的是5个、8个键盘,修改的地方就更多啦(当然你可以用数组和循环,那不在这个例子的讨论范围内)。有没有一种方法能够尽量减少变更成本呢,这就是简单工厂模式的应用需求之一——减少客户程序对类创建过程的依赖。可以添加如下设计:

class KeyboardFactory
{
public:
    Keyboard* getKeyboard(string type)  // 获取短线键盘
    {
        if ( type == "short")
            return new Keyboard(1);
        else if ( type == "long")
            return new Keyboard(5);
    }
};    

 

则获取键盘的代码变成了:

KeyboardFactory* factory = new KeyboardFactory();

// 获取短线键盘
Keyboard* k1 = factory->getKeyboard("short");
Keyboard* k2 = factory->getKeyboard("short");

// 获取长线键盘
Keyboard* k3 = factory->getKeyboard("long");
Keyboard* k4 = factory->getKeyboard("long");

这就相当于采购时不需要操心两种键盘的具体生产过程,只需要交给专业的生产者去打理,即使更改,只需要更改工厂内生产方法,并不影响用户端的代码,解除了用户端代码对具体类创建的依赖,也减小类更改的成本,这就是简单工厂模式,本质就是在键盘类和它的使用者之间加了一层用于处理创建过程。

简单工厂的应用不仅仅是对应一个类的不同参数实例,也可以多个类(例如有线键盘和无线键盘,getKeyboard ( )根据传入的参数决定实例化不同的键盘类);

从上述代码可以推出简单工厂的一个特点:每次扩展时,需要添加一个类,并修改工厂类代码,给get方法添加一条分支

 

到这里,问题又来了,假如某个采购员忽然觉得不需要长线键盘了,向全部把之前声明的长线改成短线,该怎么改? 按照目前的思路有两种方法,1是把工厂改了,getKeyboard(”long“)我也给你返回短线键盘,这样当然不是个办法,会影响其他用到长线键盘的用户;2是逐行把这个采购员调用的getKeyboard( "long") 改成 getKeyboard( "short"),如果这样的行很多的话,麻烦就大了。假如我们一开始设计工厂的时候,直接就把短线键盘和长线键盘分到两个不同工厂去生产,这个问题就解决了:

class KeyboardFactory  // 键盘工厂
{
public:
    virtual Keyboard* getKeyboard() = 0;  // 获取键盘
};
class ShortKeyboardFactory:KeyboardFactory  // 短线键盘工厂
{
public:
    Keyboard* getKeyboard()  // 获取键盘
    {
        return new Keyboard(1);
    }
};

class LongKeyboardFactory:KeyboardFactory  // 长线键盘工厂
{
public:
    Keyboard* getKeyboard()  // 获取键盘
    {
        return new Keyboard(5);
    }
};

采购员代码:

KeyboardFactory* factory = new ShortKeyboardFactory();
// 获取短线键盘
Keyboard* k1 = factory->getKeyboard();
Keyboard* k2 = factory->getKeyboard();

// 换一家工厂
delete factory;
factory = new LongKeyboardFactory();

// 获取长线键盘
Keyboard* k3 = factory->getKeyboard();
Keyboard* k4 = factory->getKeyboard();

 当需要改采购类型的时候,不需要每行都修改,只需要将

factory = new LongKeyboardFactory();

这一行改了就行,这就是工厂模式,它与简单工厂的区别就在于有多个工厂,每个工厂只专注生产一种产品,当需要修改获取的产品时,只需要修改所访问的工厂就行。

 

接下来再用一个例子来说明工厂模式,模拟另一个场景,假设有一个程序员,工作时需要使用鼠标和键盘,鼠标的功能是点击,键盘的功能是按键,我会首先设计如下两个类:

 1  class Mouse  // 鼠标类
 2  
 3  public:
 4         void virtual click() = 0;  // 鼠标的必备功能——点击
 5  };
 6  
 7  class Keyboard  // 键盘类
 8  {
 9  public:
10         void virtual press() = 0;  // 键盘的必备功能——按键
11 };

上述两个是基类,实际上是虚基类,也就是C++中实现接口的方式,不懂的请另行查阅C++虚基类的资料。

有了两个基类,考虑实际应用中会有不同类型的鼠标和键盘,可以分为有线鼠标、无线鼠标,有线键盘、无线键盘,设计类如下:

 1 class WiredMouse  // 有线鼠标
 2 {
 3 public:
 4     void click() { cout << "click wired mouse" << endl; }
 5 };
 6 
 7 class WirelessMouse  // 无线鼠标
 8 {
 9 public:
10     void click() { cout << "click wireless mouse" << endl; }
11 };
12 
13 class WiredKeyboard  // 有线键盘
14 {
15 public:
16     void press() { cout << "press wired keyboard" << endl; }
17 };
18 
19 class WirelessKeyboard  // 无线键盘
20 {
21 public:
22     void virtual press() { cout << "press wireless keyboard" << endl; }
23 };

使用这些鼠标键盘的代码如下:

1 // 获取鼠标键盘
2 Mouse* mouse1 = new WiredMouse();
3 Keyboard* keyboard1 = new WiredKeyboard();
4 
5 // 使用鼠标键盘
6 mouse1->click();
7 keyboard1->press();

看起来很容易,即使程序员忽然向改成用无线鼠标,只需要把第2行的new语句改成对应的无线鼠标类就好了。但是如果这个程序员特别无聊,非要在电脑上插3个鼠标3个键盘,代码就变成这样:

 1 // 获取鼠标键盘
 2 Mouse* mouse1 = new WiredMouse();
 3 Mouse* mouse2 = new WiredMouse();
 4 Mouse* mouse3 = new WiredMouse();
 5 
 6 Keyboard* keyboard1 = new WiredKeyboard();
 7 Keyboard* keyboard2 = new WiredKeyboard();
 8 Keyboard* keyboard3 = new WiredKeyboard();
 9 
10 // 使用鼠标键盘
11 mouse1->click();
12 keyboard1->press();

然后他忽然想把所有鼠标都改成无线鼠标,那么麻烦就来了,需要将2~4行都改一遍,假如是更多的鼠标,岂不是更麻烦?(虽然这个例子很奇怪。。。)

为了避免换鼠标的麻烦,我们希望能够只改一次,就能换完所有鼠标,于是我们使用工厂模式:

class MouseFactory
{
public:
    virtual Mouse* getMouse() = 0;
};

class WiredMouseFactory : MouseFactory // 有线鼠标工厂
{
public:
    virtual Mouse* getMouse()
    {
        return new WiredMouse();
    }
};

class WirelessMouseFactory : MouseFactory  // 无线鼠标工厂
{
public:
    virtual Mouse* getMouse()
    {
        return new WirelessMouse();
    }
};

// 键盘类似
...
...

 

然后程序员这边的代码变成了:

// 获取鼠标键盘
MouseFactory* mfactory = new WiredMouseFactory();
Mouse* mouse1 = mfactory->getMouse();
Mouse* mouse2 = mfactory->getMouse();
Mouse* mouse3 = mfactory->getMouse();

KeyboardFactory* kfactory = new WiredKeyboardFactory();
Keyboard* keyboard1 = kfactory->getKeyboard();
Keyboard* keyboard2 = kfactory->getKeyboard();
Keyboard* keyboard3 = kfactory->getKeyboard();

// 使用鼠标键盘
mouse1->click();
keyboard1->press();

这样,即使需要修改鼠标类型,只需要访问另一种鼠标工厂即可。如果添加更多类型的鼠标,只需要添加对应的工厂,只要工厂满足共用的接口,那么上面这段用户代码只需要修改工厂实例即可实现。这就是工厂模式的特点之一:每次扩展时,需要添加1个类,并添加1个对应工厂。既是优点(扩展灵活,不需要修改旧的类)又是缺点(总是要编写新工厂)。我们可以把工厂模式看作简单工厂模式的一种升级版本,但注意不是任何时候都能用来替代简单工厂。

接下我们来细化需求,假如无线键盘鼠标有很多品牌,例如品牌A和品牌B,这两种品牌都有各自的鼠标键盘,按照工厂模式的特点,我们需要给每个品牌的每个设备一个独立的工厂,类设计如下:

class AWirelessMouseFactory:MouseFactory  // 品牌A无线鼠标工厂
{
public:
    virtual Mouse* getMouse()  // 提供品牌A无线鼠标
    {
        return new AWirelessMouse();
    }
};

class AWirelessKeyboardFactory:KeyboardFactory  // 品牌A无线键盘工厂
{
public:
    virtual Keyboard* getKeyboard()  // 提供品牌A无线键盘
    {
        return new AWirelessKeyboard();
    }
};

class BWirelessMouseFactory:MouseFactory  // 品牌B无线鼠标工厂
{
public:
    virtual Mouse* getMouse()  // 提供品牌B无线鼠标
    {
        return new BWirelessMouse();
    }
};

class BWirelessKeyboardFactory:KeyboardFactory  // 品牌B无线键盘工厂
{
public:
    virtual Keyboard* getKeyboard()  // 提供品牌B无线键盘
    {
        return new BWirelessKeyboard();
    }
};

假如有这么一条限制:同一个品牌的鼠标键盘必须配套使用,不能与其他品牌混用。

可能会出现下面这种情况:

// 获取鼠标键盘
MouseFactory* mfactory = new AWiredMouseFactory();
Mouse* mouse1 = mfactory->getMouse();  // 选用了A品牌鼠标

KeyboardFactory* kfactory = new BWiredKeyboardFactory();
Keyboard* keyboard1 = kfactory->getKeyboard();  // 选用类B品牌的键盘

// 使用鼠标键盘
mouse1->click();
keyboard1->press();  // 咦?用不了?

为了避免这种情况,我们可以设立品牌工厂,也就是将同一品牌多个设备的生产合并到一个工厂,工厂不再生产单一产品,所有要用到的设备都从一个工厂里购买,就不会买到不匹配的鼠标和键盘:

class AbstractFactory  // 抽象工厂接口,里面包含所有产品的get方法
{
public:
    virtual Mouse* getMouse() = 0;
    virtual Mouse* getKeyboard() = 0;
};

class AFactory : AbstractFactory // 品牌A工厂
{
public:
    virtual Mouse* getMouse()  // 提供品牌A无线鼠标
    {
        return new AWirelessMouse();
    }
    virtual Keyboard* getKeyboard()  // 提供品牌A无线键盘
    {
        return new AWirelessKeyboard();
    }
};

class BFactory : AbstractFactory // 品牌B工厂
{
public:
    virtual Mouse* getMouse()  // 提供品牌B无线鼠标
    {
        return new BWirelessMouse();
    }
    virtual Keyboard* getKeyboard()  // 提供品牌B无线键盘
    {
        return new BWirelessKeyboard();
    }
};

获取鼠标键盘的代码如下:

AbstractFactory* factory = new AFactory();  // 访问品牌A工厂
Mouse* mouse1 = factory->getMouse();
Keyboard* keyboard1 = factory->getKeyboard();

mouse1->click();
keyboard1->press();  // 使用正常

假如一个人之前一直使用的品牌A的产品,忽然想改成品牌B(这种需求很常见,比如同一套代码从windows搬到linux,某些库需要变更),只需要访问不同的专卖店即可。

可以直接把第1行改成:

AbstractFactory* factory = new BFactory();  // 访问品牌B工厂

这就是抽象工厂模式:工厂不再只有单一产品,而是同一系列的所有产品;这么设计的特点是:在已有的系列之间切换灵活(只需要修改最初访问的工厂),但扩展麻烦,每次扩展新产品时,需要在抽象工厂接口添加新产品的get方法,于是所有具体工厂都要添加这个新的get方法。例如添加一个显示器类,那么抽象工厂接口以及所有实现该接口的类都要添加一个getScreen()方法。

 

简单工厂+抽象工厂

可以将简单工厂与抽象工厂结合结合进来,设置多品牌销售代理,该代理同时销售多个品牌,相当于多个专卖店的集合,只需要一开始告知你要的品牌,由代理去帮你拿回一套产品

class Agent
{
public:
    Mouse* getMouse()
    { 
        switch(brand)
        case 'A':
                return new AWirelessMouse();
        case 'B':
                return new BWirelessMouse();
    }

    Keyboard* getKeyboard()
    { 
        switch(brand)
        case 'A':
                return new AWirelessKeyboard();
        case 'B':
                return new BWirelessKeyboard();
    }

private:
    char brand;  // 指定品牌
};

这里的switch分支是简单工厂常用的手段,而一个代理能产生多种设备又是抽象工厂的特点,因此可以认为是简爱工厂于抽象工厂的结合。

总结

简单工厂: 在类对象的请求者与类之间加一层去处理实例化,并通过分支语句来决定如何实例化;优点:解耦,能应对需求变更;缺点:扩展略麻烦,需要修改工厂类

工厂模式:每个工厂只专注一种对象的实例化;优点:解耦,能应对需求变更,扩展不需要修改旧的类;缺点:每次扩展的编码量提升,需要对应多写一个工厂类

抽象工厂:每个工厂负责同一系列的多种对象实例化;优点:解耦,在现有系列之间切换灵活;缺点:扩展产品类型时很麻烦,从头改到尾

posted @ 2016-06-03 10:11  漫游的陈小飞  阅读(1722)  评论(0编辑  收藏  举报