简单工厂模式、工厂模式、抽象工厂模式的对比与应用场景(代码举例)
简单工厂模式、工厂模式以及抽象工厂模式都是非常常用的设计模式,这段时间在读《大话设计模式》,对于这几个的模式有了自己的认识,下面就通过几个例子,一步一步整理这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分支是简单工厂常用的手段,而一个代理能产生多种设备又是抽象工厂的特点,因此可以认为是简爱工厂于抽象工厂的结合。
总结
简单工厂: 在类对象的请求者与类之间加一层去处理实例化,并通过分支语句来决定如何实例化;优点:解耦,能应对需求变更;缺点:扩展略麻烦,需要修改工厂类
工厂模式:每个工厂只专注一种对象的实例化;优点:解耦,能应对需求变更,扩展不需要修改旧的类;缺点:每次扩展的编码量提升,需要对应多写一个工厂类
抽象工厂:每个工厂负责同一系列的多种对象实例化;优点:解耦,在现有系列之间切换灵活;缺点:扩展产品类型时很麻烦,从头改到尾