博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
简单工厂模式、工厂模式、抽象工厂模式的对比与应用场景(代码举例)
阅读量:6811 次
发布时间:2019-06-26

本文共 8118 字,大约阅读时间需要 27 分钟。

简单工厂模式、工厂模式以及抽象工厂模式都是非常常用的设计模式,这段时间在读《大话设计模式》,对于这几个的模式有了自己的认识,下面就通过几个例子,一步一步整理这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分支是简单工厂常用的手段,而一个代理能产生多种设备又是抽象工厂的特点,因此可以认为是简爱工厂于抽象工厂的结合。

总结

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

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

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

转载于:https://www.cnblogs.com/cyf333333/p/5554151.html

你可能感兴趣的文章
音频编码 Audio Converter
查看>>
SQL - case when then else end 的用法
查看>>
web优化是http缓存(上)
查看>>
19-01-14
查看>>
媒体融合三部曲(未完待续...)
查看>>
OkHttp3-拦截器(Interceptor)
查看>>
Bootstrap在实际生产开发中的使用心得
查看>>
Google推出实时内容洞察工具 为用户提供表现最好的内容
查看>>
虚拟机故障与故障处理工具之指令篇
查看>>
iOS 基础知识学习目录索引
查看>>
My_Base_notes
查看>>
Node assert断言学习及mocha框架与travisCI初探
查看>>
大话转岗 PHP 开发小结
查看>>
React的状态管理
查看>>
寻找一种易于理解的一致性算法(扩展版)下
查看>>
MySQL - 高可用性:少宕机即高可用?
查看>>
php 生成唯一值
查看>>
2018电影票房分析-谁才是票房之王
查看>>
程序员可以干到多少岁?
查看>>
Storm系列(六)storm和kafka集成
查看>>