设计模式:简单工厂
吴剑 2013-05-21
原创文章,转载必需注明出处:http://www.cnblogs.com/wu-jian
概述
从设计模式的类型上来说,简单工厂模式属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。
为什么需要使用工厂?
从一个简单的例子开始吧,假设一家小军火商出售枪支(尼古拉斯凯奇有部电影叫《战争之王》),他们拥有M16和AK47两种库存,如下代码所示:
using System; //军火商 namespace WuJian.DesignMode.SimpleFactory.NoFactoryDemo.ArmsDealer { //苏联卡拉什尼科夫设计的世界最著名的突击步枪 public class AK47 { //射击 public void Firing() { Console.WriteLine("ak47 firing ..."); } //装弹 public void Loading() { Console.WriteLine("ak47 loading ..."); } } //M16是第二次世界大战后美国换装的第二代步枪,被誉为当今世界六大名枪之一 public class M16 { public void Firing() { Console.WriteLine("m16 firing ..."); } public void Loading() { Console.WriteLine("m16 loading ..."); } } }
客户调用:
//索马里海盗 public static class Somali { public static void Buy() { //购买一把AK47 AK47 ak47 = new AK47(); ak47.Firing(); //购买一把M16 M16 m16 = new M16(); m16.Firing(); } }
OK,如上代码不论对小军火商还是小军火商的客户索马里海盗来说,足够简单且满足使用。
军工行业虽然比不上IT行业日新月异,但变化还是存在的,没多久索马里海盗集团觉得M16太贵,AK47又不上档次,他们要求批量采购中国的95式步枪;同时VIP客户朝鲜金胖自卫队要求采购一批沙漠之鹰......各种新需求接踵而至,于是小军火商变成了中军火商,枪械种类很快超过了100种。
在OO的软件设计开发中有条古训:找出变化的部分并对其进行封装。继续上面的例子,作为商人军火商肯定关心利润,当小军火商成为中军火商时,有一天BOSS找到CTO(首席技术官),说你给我做个统计吧,以后每卖出一把枪,你给我记个账。
按上面的代码,需要修改每个枪械类的构造函数,每个类每new一次,统计一下,超过100个类每个都得修改。
public class AK47 { public AK47() { //统计一下 } } public class M16 { public M16() { //统计一下 } } //......统计一百多下
CTO不是天生出来就是CTO,经过这一次修改,他觉得new也属于变化部分,需要单独封装。于是,CTO的职业技能有了进步,关于工厂模式的讨论也有了铺垫。
工厂模式登场
在OOD(Object-Oriented Design,面向对象设计)中有很多需要遵循的基本法则,虽然这些法则不是尚方宝剑,但积累了前人的经验和总结。其中开闭原则无疑是最重要之一,因为很多其它原则都是围绕开闭原则展开的。这不难理解,我们设计软件,总是希望最少的修改满足最大的变化。
看看开闭原则的定义:软件中的功能模块应该对扩展开放,对修改封闭。这句话似乎比较抽象,还是接着聊上面的军火商会比较容易理解一些:
如上图所示,整个系统目前包含了简单的两个部分:军火商和军火商的客户。他们面临相同的一个问题:变化!
对于军火商的客户来说,他能new出来的每个枪械对象前提条件是必须知道对应枪械类的存在。当军火商的枪械类发生变化时,比如突然下架了AK47,索马里的代码就必须修改,比如删掉new AK47的那部分。所以对于索马里来说,军火商的系统并不厚道,因为它导至了索马里的代码不能对修改关闭。
再看军火商的部分,前面军火商的CTO已经经历了对每个类添加统计的惨痛教训。拿上回的统计需求来说事儿,每个枪械类(构造函数)也不是对修改封闭的。当然,所有的软件设计原则以及本文所举的例子都是相对的,我们没有办法做到每个类都绝对对修改关闭,俗话说计划没变化快,变化没电话快,但一个优秀的架构师需要提早发现并封装更多的变化部分。
OK,看看工厂的实现以及工厂如何做到对客户端的修改关闭以及如何解决军火商BOSS的统计需求(关闭枪械类构造函数部分的修改),如下代码:
using System; namespace WuJian.DesignMode.SimpleFactory.SimpleFactoryDemo.ArmsDealer { //简单工厂 public static class GunFactory { public static T GetGun<T>() where T : IGun, new() { //统计代码略 //... //AK47已售完 if(typeof(T).Equals(typeof(AK47))){ return default(T); } //创建对象相关的变化均封装在工厂中 //... return new T(); } } //抽象 public interface IGun { //射击 void Firing(); //装弹 void Loading(); } public class AK47 : IGun { public void Firing() { Console.WriteLine("ak47 firing ..."); } public void Loading() { Console.WriteLine("ak47 loading ..."); } } public class M16 : IGun { public void Firing() { Console.WriteLine("m16 firing ..."); } public void Loading() { Console.WriteLine("m16 loading ..."); } } }
客户端调用:
public static void Buy() { AK47 ak47 = GunFactory.GetGun<AK47>(); if (ak47 != null) ak47.Firing(); else Console.WriteLine("ak47无货."); M16 m16 = GunFactory.GetGun<M16>(); if(m16 != null) m16.Firing(); else Console.WriteLine("m16无货."); }
首先从代码中可以看到,统计功能封装在了工厂中,不光统计,以后只要是对象创建相关的变化,都只需要修改工厂,而不需要修改每个类的构造函数了。
然后举了一个比较特殊的例子,当AK47或任何其它一款产品停止供应时,客户端不再需要变更代码。
最后是简单工厂的实现,GunFactory类中我使用了泛型。当然也可以传递字符串进来匹配创建对应的对象;还可以直接定义静态方法,如GetAK47(),GetM16()。其实什么方式并不重要,重要的是将创建对象的逻辑封装在了一个静态GunFactory类中,这就是简单工厂的核心宗旨。
除此之外代码中还多了一个名为IGun的接口,它是对所有枪械类的抽象。工厂尽量创建抽象,而不创建具体,这能增强工厂的可复用性,减少与具体类的耦合,为后续提供更好的扩展,这部分有兴趣的读者可了解依赖倒置原则。
OK,关于简单工厂的学习告一阶段,本文为自己学习的一个总结备忘,如能给人予帮助,不斟荣幸。同时个人能力有限,不足之处,请及时指定。
相关资料
简单工厂:http://baike.baidu.com/view/1227908.htm
开闭原则:http://zh.wikipedia.org/wiki/%E5%BC%80%E9%97%AD%E5%8E%9F%E5%88%99
依赖倒置原则:http://baike.baidu.com/view/2503920.htm
DEMO下载
DEMO运行环境:Windows 7、Visual Studio 2012、.Net Framework 4.5
点击下载DEMO
<全文完>
作者:吴剑
出处:http://www.cnblogs.com/wu-jian/
本文版权归作者和博客园共有,欢迎转载,但必需注明出处,并且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。