重温设计模式(四)——工厂模式
1. 写在前面
这篇文章是我酝酿了近一个月的时间写出来的,我想在第一个模式上就写出工厂模式,却一直推迟到现在。
工厂模式,看似很容易,很常见,但是学习设计模式两年,我至今仍未能真正地充分理解工厂模式的意图。工厂模式好在哪,他究竟解决了什么问题,工厂还是不工厂,困扰了我整整两年。
从无模式,到为模式而模式,在到今天的重温设计模式。我已经记不清大多数模式的样式了,只记得一系列的设计原则,然后去思考模式的综合。但是工厂模式,至今我仍然难以理解。
写出这篇文章,更多的目的是为了抛砖引玉,希望大家能够多多指教。(初学者选学)
2. 从简单工厂谈起
简单工厂是工厂模式的简化,很多人说他并不是模式,我却不这样认为。相反,我认为简单工厂在某种程度,某些场合上,比起略重量级的工厂模式更为合适。
简单工厂就像一些外包公司,他们没有固定的项目方向,只要给他们一个项目,他们就会去做,而不管这个项目的类型。
让我们先来看看简单工厂的UML图:
3. 简单工厂示例
就写出我们上面UML图的例子了:
class Program { static void Main(string[] args) { Product product = Factory.CreatePruduct(Type.ProductA); } } enum Type { ProductA, ProductB } class Factory { public static Product CreatePruduct(Type t) { switch (t) { case Type.ProductA: return new ProductA(); break; case Type.ProductB: return new ProductB(); break; } } } abstract class Product { } class ProductA : Product { } class ProductB:Product { }让我们来看下,简单工厂的特点:
他把对象的创建都封装到了一个简单工厂类里,然后让客户端和工厂类发生耦合,而脱离了和具体对象的耦合关系。
优点和缺点暂且不说,这个我们在下文中对比着去看。
4. 进入工厂模式
工厂模式与简单工厂的最大特点就是单一了简单工厂的职责,由外包公司成功转型为行业性软件公司。每个工厂只生产一类产品,这样的优点是取出了简单工厂中丑陋的switch-case或者是if-else。
看下结构图:
我觉得这个图不能很好地表达出工厂方法的意思。让我们先继续向下看:
工厂模式定义一个用户创建对象的接口,让子类去决定实例化哪一个类。
5. 工厂模式实例
让我们来“改造”那个简单工厂的例子:
class Program { static void Main(string[] args) { Factory factory = new FactoryA(); Product product = factory.CreatePruduct(); } } enum Type { ProductA, ProductB } abstract class Factory { public abstract Product CreatePruduct(); } class FactoryA : Factory { public override Product CreatePruduct() { return new ProductA(); } } class FactoryB : Factory { public override Product CreatePruduct() { return new ProductB(); } } abstract class Product { } class ProductA : Product { } class ProductB:Product { }让我们来总结下工厂模式的特点:
工厂模式把简单工厂从核心地位给拉了出来,把创建的权利又交还到客户手中,从而让整个项目的核心转移。
6. 无模式的实现
class Program { static void Main(string[] args) { Product product = new ProductA(); } } abstract class Product { } class ProductA : Product { } class ProductB : Product { }7. 工厂方法大比拼
本节是全文的核心,我想也是最容易引起争论的地方,在这里,我只表明我自己的观点,也欢迎大家多提意见!
如果单单从上文的例子来看,使用工厂方法未免有了为了模式而模式之嫌。
假设我们的需求改为了制造一个ProductB的对象。让我们来看看3种模式的大比拼:
<1> 简单工厂模式需要在客户端发生变动:
static void Main(string[] args) { Factory factory = new Factory(); //factory.CreatePruduct(Type.ProductA); factory.CreatePruduct(Type.ProductB); }<2> 工厂模式需要在客户端发生变动:
static void Main(string[] args) { //Factory f = new FactoryA(); Factory f = new FactoryB(); f.CreatePruduct(); }<3> 若不用设计模式:
static void Main(string[] args) { //Product product = new ProductA(); Product product = new ProductB(); }三种方式没有什么区别。也就是说,如果在客户端只有一个调用,而且只需要简单的一步初始化,我们完全没有必要为了模式而模式。
接下来,我们假设在客户端存在多个位置需要创造一个Product对象。
<1> 简单工厂模式: 我们需要在每个客户端处都进行相应的修改,传入的参数,没有什么好办法。也就是说我们需要进行N个位置的修改。
<2> 工厂方法模式: 我们可以将Factory的初始化移在单独的一个模块来进行,模拟代码如下:
Factory.dll:
public static Factory CreateFactory() { return new FactoryA(); }Client端:
static void Main(string[] args) { Factory factory = CreateFactory(); Product product = factory.CreatePruduct(); }这样,我们把工厂类单独封装,于是我们在修改的时候只需要对Factory.dll文件进行修改重编译,而不需要在客户端做任何改动。也就是说我们只需要一处的修改。
<3> 无模式的方法:我们的做法依然仿照工厂方法模式的去做,模拟代码如下:
CreateProduct.dll:
public static Product CreateProduct() { return new ProductA(); }Client端:
static void Main(string[] args) { Product product = CreateProduct(); }我们依旧只需要修改CreateProduct.dll,而不需要对任何客户端代码进行修改。也就是说,我们只需要在一处进行修改即可。
这种情况下,当然这也是最常见的情况下,三种模式的PK,简单工厂完败,而我并没有看出工厂模式较之无模式哪里占了上风。
接下来,我们再看最后一种情况,就是增加一套产品ProductC,然后修改为对ProductC的引用。
这里我就不再罗哩罗嗦地粘代码了,直接来说:
<1> 简单工厂需要修改enum条件,然后需要修改Factory中的case条件,需要修改在N个客户端的调用。
<2> 工厂模式仍然是将创造工厂封装成一个dll,然后需要针对产品增加工厂,然后在dll处修改代码。
<3> 无模式情况下只需要增加一个ProductC类,然后修改CreateProduct.dll即可。
结果与上述区别并不大,简单工厂仍然败阵,而无模式较之工厂模式却又简介了很多,而且并没有影响到其扩展性。
那么究竟工厂模式好在哪?他有什么优点?他应该用于何处?
8. 工厂模式,请勿滥用
相比于其他模式,工厂模式是最被滥用的一个。只要有了if---else,就去工厂模式,只要涉及到选择性创造对象,就去工厂模式。似乎工厂模式成了解决一切的万能灵药。
可是让我们看看上面的一些对比,用了工厂模式后,你的代码简单了么?错!恰恰相反,在没有提高代码扩展性的同时,却又增加了类的调用次数,增加了类的个数。这不是我们希望看到的!
9. 反射为工厂而生?
《大话设计模式》上有一句话让我印象颇深,反射反射,程序员的快乐。
更有人说,反射+工厂模式==完美的开放封闭。
还有人说,没有了工厂的反射,就是在乱射一气,反射因为工厂而成为了一项有用的技术。
可是,真的是这样么?
在这里,我就不去写那些耳熟能详的配置文件+反射的代码了。
反射+工厂无非如下:<add key=”Product” value=”ProductA”>。接下来,我们去创造工厂的时候去创造一个[Product]+”Factory”的工厂。然后用工厂去创造产品对象实例。
然而,没有了工厂,反射是在乱反一气么?
让我们看看无模式+反射:配置文件的内容依然如上,接下来,我们只需要在产生产品的CreateProduct.dll中去CreateInstance([Product])即可了。
有什么不好么?至少我没看出来。
总之,反射并非为工厂而生。
10. 工厂究竟用于何处
本节是全文的重点讨论之处,也是难点,是我自己都没太搞明白的知识点。
在这里,我再次声明,我只声明我自己的观点,不敢保证正确与否,初学者慎入!
<1> 很多对象的的创建并不仅仅是一个new那么简单,而是需要很多复杂的步骤,这样,我们如果写成一个工厂,我们就可以根据不同的需求调用不同的工厂,但是如果我们不使用模式,就需要频繁地去改动CreateProduct.dll这个创建产品的类,而这个时候,创建产品不仅仅是修改一个new这样简单,而是需要很多的过程。
这个时候,工厂模式优之很多。但我一时想不出来这样的一个例子,还希望大家指教。
<2> 本条纯属本人扯淡,请选择性参考。我是个思考大于实践的人,心里有好多奇怪的甚至荒谬的想法,但却一直没有机会去VS下,而且这样的念头常常一闪而过,也许是路上,也许是梦里。
我想过用装饰模式或者建造者模式等去综合地构建出一个大对象,而这个对象并非实例化出来,而是由装饰模式和建造者模式构造出来,其实这条和上条很相近,只是我给出了略微具体些的对象细节罢了。他需要一个复杂的创建过程,也就是模式套模式,形成一个模式套。如此下来,这个复杂的创建过程当然要放到工厂中,原因如<1>。
<3> 团队开发中,设计人员往往定义中间层接口,然后交给开发人员去实现,就用Petshop的数据库访问类,如OracleHelper,SQLHelper之类来说话。我的上层调用人员只需要Helper sqlHelper = factory.CreateHelper(“SQL”)去得到SQLHelper,而SQLHelper开发人员只需要继承自Helper抽象类。
相反,如果我们不用工厂模式,上层调用人员就需要Helper helper=new SQLHelper();但是很可能的情况是,他并不知道Helper开发人员要给这个类起什么名字,也许是SQLHelper,也可能是HelperSQL。
这样才叫减小了两层之间的耦合度,增加了项目的透明性,就是这样。
11. 工厂模式一定有工厂么?
其实,模式这个东西我不愿意给他起很多很多的名字,拿出一个东西,知道该怎么去设计最优,这就足够了。
但是在这里,我还是把这个拿出来说一下。工厂模式,一定要有工厂么?
什么是工厂?能创造出产品的就是工厂。软件公司,我们一样可以把他叫做软件工厂,因为他产出了软件。
既然这样,那么我上面的CreateProduct.dll。我也一样可以把他理解为工厂,因为他产出了我需要的产品,就是这么简单。
总之,工厂模式不一定偏要去写***Factory的类,只需要找出一个最好的方法,让这个方法能够根据我们的需求造出我们需要的产品,这就是一个好的工厂。
12. 再谈模式
模式是个什么东西?模式是前人经验的总结,对于某些情况,他们给出了一个他们的意见,但是当情况不符合时,他的模式不见得是最好的模式。
GOF可以创造出23种设计模式,我们每个人的心中也许还都有自己的一套设计模式,也许远远超过23种,只是因为我们不是牛人,所以我们不能把自己的模式起个名字写本书放进去,至此而已。
说到这个,让我想起了我之前的一篇设计模式的文章:重温设计模式(二)——桥接模式(Bridge)。很多人说这不是桥接模式,桥接模式应该怎么怎么样,这个更像什么什么模式。我觉得这是完全没有必要的。
还是那句话,每个人的心中都有自己的一套模式,GOF的模式给我们提供了一个模板,他告诉我们面向对象是可扩展的,他同样告诉我们,设计模式是可扩展的。至于那个是不是桥接模式,我在这里并不想再去讨论,有的人说,这个像装饰模式,装饰模式怎么怎么样,解决了什么什么什么什么。但是,我的这个问题,用这种方法是最容易解决的,那么他对我来说就是一个最好的模式,而这个模式的中心就是组合优于继承,那么我就说,他是一个桥接模式。这本就无可争议。
13. 工厂不工厂
**不**成了我很爱用的一句话,貌似是来源于一首歌中的:简简单单地说,爱是不爱。
爱是不爱,工厂不工厂。To be or not to be , It is a question.
模式本身并不难,难的是我们如何去理解模式;难的是我们如何把模式融会贯通,无刀胜有刀;难的是我们如何在项目中去合理设计,而不是为了模式而模式。
我们究竟是否工厂。下面相当于我对上面我的观点的总结:
1. 工厂越轻量级越好,能不Factory 就尽量不要去Factory。
2. 如果整个项目不那么大,层与层之间对开发者不透明,那么我们就不要去工厂。
3. 如果只是简单类的new创建,那么就不要去工厂。
接下来,让我们的设计师去检查检查自己的设计,是不是多了太多的工厂呢?
14. 疑似广告——学习设计模式的书籍问题
关于设计模式方面的书籍,我看了如下的几本:
<1> 《大话设计模式》 (伍迷)
<2> 《设计模式——可复用的面向对象******》(名字突然想不起来了,GOF)
<3> 《Head first 设计模式》
<4> 《设计模式——基于C#的工程化实现及扩展》
<5> 《Java 与 模式》
剩下就是在博客园上TerryLee以及怪怪的一些高论。
我接下来对上面进行下我的看法总结,冒犯之处,还请见谅。
《大话设计模式》通俗易懂,里面的例子确实很贴近我们的生活,但是他只适合一个入门,并不能在设计模式的路上只看那一本书,正如书中所说,他是为了GOF的书打下基础。这本书,我至少看了五遍,上面了例子,也是敲了四五遍,以至于今后谁说出一种模式,我第一个反映在脑海中的并不是这个模式的内容,而是这个模式的故事。可以说,是这本书打下了我设计模式的基础。
GOF设计模式相对来说很难看懂,也许是因为我C++的确差劲,这本书,也花了我最长的时间,看了两遍。书中的例子很经典,这本书是必看的,我就不在多说。
Head first是我茶余饭后随手翻翻的书,我个人对此书并无好感。
蜡笔小王的设计模式,这本书我认为最适合在看过GOF的书之后看,书上很多的例子以实际工程项目的例子去阐述,让我获益匪浅,建议此书必看!
Java与模式,这书几乎是所有学习Java的人必备的人手一份的书,上面例子的综合让我不得不对作者崇拜至极,但是我觉得也许作者过于强调传统文化,以至于很多模式和原则的讲解有些牵强。
最后是怪怪的文章,也许是我水平有限,怪怪的文章我一向很难理解,以致与一篇文章我一般要看上十遍以上方能看出怪怪想表达出的意思。但是一旦看懂,便是获益匪浅!
15. 个人总结
写到这,文章也该结尾了。终于写完了最让我难以理解的工厂模式,但是在写的过程中,我又想到了很多东西。
最后,总结:工厂模式就是将创建的过程与客户端相分离,而将他们封装在工厂类里。从而避免了客户端与产品类的直接耦合关系。而简单工厂之所以被废弃则是因为他集中了太多的功能,而成为了整个项目的核心,从而违背了单一职责原则。
工厂有优点亦有缺点,工厂不工厂,It’s a question。