各位小牛大牛老鸟菜鸟们好,欢迎参观个人设计模式世界。这个世界我已经总结多年了,如今才刚刚成型。But I have a dream,梦想全部开发者都能一晚上之间认清全部设计模式,还幻想之后你们认识设计模式时,必首先google本文,嘿嘿。html
前辈同仁们已经总结过不少,至今首页上设计模式的文章仍然层出不穷。但我总认为,在GOF的23个设计模式提出多年了,该须要些变化和扩展了。特别适用于.NET(或Mono)的设计模式,好像没有系统的总结。新年伊始,推出这篇总结,我我的不喜欢人云亦云,对设计模式的总体,以及其中一些模式,有独特的理解,欢迎指点。程序员
引用阿彬同窗对术和道的论述:在真正好的程序员心中,设计模式是“术”,设计模式背后的用意才是“道”。为何要用某个模式,是为了解决什么问题?紧耦合?可测性差?扩展性差?他们写代码时,心中已无设计模式,用心设计、简单设计;在可测性、可扩展性和复杂程度之间作巧妙的权衡取舍;当他们写完代码,已经不知不觉合理地使用了若干设计模式。——其实我这句话也说错了,代码永远没有“写完”的一天,他们会不断重构它,重构出设计模式,或者重构掉设计模式。web
本文所列的设计模式基于维基百科,有些我认为是鱼目混珠,另外补充了几个模式。我认识设计模式的方式有点不一样,不想照搬一个模式的定义,更关心它的特征,即长得像什么,能作什么,为何要这样作,这应该更方便新人理解。此外,还设法注意不一样设计模式之间的联系。因为时间篇幅关系,没法贴出太多代码,不过会指出多数模式在.NET BCL中的实现。面试
设计模式通常分为建立、构造、行为三类。我遵循这个分类,而作了一点调整,如Builder模式我放到构造分类中。 加上并发模式,共四类。并发类的模式稍后再补充。数据库
除这个传统的分类方式外,我会从另外三个角度为设计模式归纳总结。编程
第一个角度就是语言的角度,本文主要基于C#/VB.NET(Mono)。设计模式并不是架构模式,它与语言特性紧密相关。很多模式在不一样语言实现不一样,好比单例模式。有些模式则是某种语言独有的,好比RAII是C++专用模式。设计模式
第二个角度,是开发领域角度。编程开发可简单分为两大领域,即框架开发和应用开发。明白这两个领域的区别和联系,对理解设计模式的实现很是重要。在框架开发中,设计模式应用频率要高得多,有些模式基本上只出现于框架中。作应用开发,常常会“应用”框架实现的设计模式,很多人用了而也稀里糊涂地不知道。跨域
“应用”可能不算很确切的词儿,仍是来个图吧,表示一般状况下,框架层和应用层合做的设计模式完整实现:缓存
我仍是先举个最简单的Entity Framework的例子吧,省得被鄙视。现在EF比较钟情Code First,代码以下:网络
public class PersonMap //这是对抽象的扩展 : EntityTypeConfiguration<Person> //这是EF提供的抽象 { public PersonMap() { this.HasKey(p => p.IDCard); //设IDCard为主键属性 } } public class MyDbContext : DbContext { public MyDbContext() : base("Entities") { } /// <summary> /// 在这个方法里能作的事情,就是API /// </summary> protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.Add(new PersonMap()); //这天然就是“应用” } public DbSet<Person> Persons { get; set; } }
至于这是哪一种模式,但愿把本文看完就明白了。
不是作应用开发就没必要了解设计模式,偏偏相反,作应用开发由于接触得少,更须要主动了解设计模式,以避免成日陷于业务海洋中成了砖家,专门搬砖盖永远盖不到顶的业务大楼。并且有一些模式,应用开发中用的更频繁。
无论是应用仍是建立完整的实现,均可以说使用了这个模式。面试官问你用过哪些模式的时候,随便拾三两个例子就行,别让人家太崇拜你,嘿嘿。
咱们从上图可见,设计模式实现分为四部分。API和抽象能够合算一部分,应该称之“采用某设计模式的解决方案”,简称解决方案吧。然而不是全部模式的解决方案都须要抽象部分,没有抽象天然也没有扩展。根据设计模式是否提供扩展机制这点,能够分为扩展模式和非扩展模式,这就是第三个角度。可扩展的模式理解难度略大,不过也更有趣。
通常地讲,构造模式都是可扩展的,行为模式都是非扩展的,而建立模式两者兼有。
1、建立模式 Creational patterns
1. 工厂方法模式 Factory Method
这个模式很简单,就是不直接经过构造函数(用new关键字),而是经过调用某个方法(偶尔用属性)获得一个对象。好比WebRequest.Create(url)方法,返回HttpWebRequest对象。
为何要用方法而不是new一个对象呢?三种缘由,一就是不想建立新的对象;二是建立新对象的逻辑比较复杂,复杂致使出错建立失败几率较高,而构造函数应该尽可能避免异常(疑似沿自C++的潜规则),因此习惯上推荐用方法建立,如Image类FromFile和FromHandle方法;三是为了方便,好比 Stopwatch.StartNew()方法和Task.Factory.StartNew(action)方法。
下面许多其余模式都会用工厂方法,由于咱们每每不知不觉就在用它,就不列举了。这是一个最基础最底层的模式。
灵活应用泛型可让大大减小建立工厂方法的工做,如用Comparer<T>.Create(Comparion<T>)方法,不用写不一样类型的比较器类,就能建立出针对不一样类型的比较器。
2. 抽象工厂模式 Abstract Factory
工厂就是指那种专门提供工厂方法的类。若是业务逻辑复杂起来,而若是想根据须要,分别获得某一批而不是某一个类的实例,就须要有相应的一批工厂类。若是用抽象类或接口规定了这批工厂类的工厂方法,就是抽象工厂模式。通常来讲,若工厂源自抽象工厂,其“产品”也继承自某抽象类或接口。
此模式比上个复杂得多,但其实很好理解。曹操曾吟,对酒当歌,人生几何。何以解忧,惟有杜康。他必定想不到现在还有拉菲产葡萄酒,茅台产白酒,青啤产啤酒。若是曹公再世,粉丝想为他写个程序,就能够定义一个抽象工厂:酒厂,拉菲等品牌继承自酒厂,产品白酒啤酒都是酒,继承自酒类。
上面提到的Task.Factory,是一个TaskFactory对象。像TaskFactory这种独立工厂很少见,由于若只要获得某个特定类的实例,一般像WebRequest/Image那样在自身加一个静态工厂方法便可。
这是个你们最熟悉的模式,被普遍应用在框架设计中,下表列了几个BCL的例子。这种模式很好识别,由于工厂类名多带Factory,和接口开头的I,成了标准,不过也有一些深藏不露。
抽象工厂 |
工厂类 |
产品定义 |
产品类 |
IHttpHandler |
Page, StaticFileHandler 等 |
||
IControllerFactory |
IController |
Controller |
|
SqlDbFactory |
DbCommand, DbDataAdapter 等 |
SqlCommand/OleDbCommand… |
|
IEnumerable<T> |
List<T>, HashSet<T>等 |
IEnumerator<T> |
Listt`1+Enumerator<T> |
IEnumerable<T>接口和List/HashSet,虽然不少人没想到,倒是彻底标准的抽象工厂及实现。上个模式提到的Compare<T>,其实也是抽象工厂的变种实现,实现IComparer<T>的类,如 StringComparer/GenericComparer<T>,不是产生对象,而是对对象做处理统计,其实算另外一种设计模式,也能够认为是一个“工厂”。
传统上,该模式还须要一个获取具体工厂实例的工厂方法。在应用开发中,使用StructureMap等IoC框架能够替代这种方法,甚至无须明确调用工厂类。
3. 延迟加载模式 Lazy initialization
这也是咱们不知不觉在用的模式。顾名思义,声明某个属性或变量时不当即初始化,直至用到才加载。这个模式用几行代码就能说明:
class ObjectWithLargeObjectProperty { LargeObject largeObjectField; public LargeObject LargeObject { get { if (largeObjectField == null) largeObjectField = new LargeObject(); //核心是该行 return largeObjectField; } } }
在用户较多的应用系统中,要考虑并发的影响。应该尽可能采用Lazy<T>类(.NET 4.0+),能够选择并发时的建立对象策略。
4.单例模式 Singleton
让一个类只能有一个实例,通常经过静态属性提供。这种类都是管理系统全局运行时信息,有时也能够用静态类代替,而用单例主要考虑扩展性和可测试性。 Comparer<T>.Default就是典型的单例模式。过去觉得像单例的是HttpContext.Current,只是一个像工厂的静态属性。
这种模式很常见。至于如何实现,首先必须让构造函数变成私有的private。我看到许多例子用Lazy Initilization,还有双重锁,我的很不推荐。由于这种全局单例确定是系统运行必须且在通常系统启动时就会初始化,因此简单用静态构造函数或静态字段初始化便可,并保证并发性,就是Terry Lee的文章最后一种实现。
5.对象池模式 Object Pool
在应用开发中不多见,但框架开发中较多,由于使用该对象池的类通常涉及管理非托管资源。 想获取某个类的实例,优先从池中取,若须要新建实例,也放入池中,用完后只是标志其为空闲状态以备用。
ThreadPool类是一个摆明典型实现。还有DbConnection及其子类,使用链接池。在不是.NET,但息息相关的IIS中会用到ApplicationPool。
全部Object Pool都有一个大小限制。不管是实现仍是引用该模式,都要注意尽量地释放池中对象。对于数据库链接要合理关闭,对于Asp.NET能够考虑用 IHttpAsnycHandler和IAsyncController(MVC)减小ApplicationPool的负载。
6. 享元模式 Fly Weight
以为该模式相似对象池,都是为了复用对象,多用于框架设计中。是一种建立模式,因此在放在这里。区别一是该模式不会指定对象数量上限,二是保存共享对象的数据结构相似于字典,经过工厂方法传递过来的key,找到字典的既有对象,若没找到则新建并存入字典中。
这是应用缓存的一种场景。若是有过时控制需求的话,能够直接用MemoryCache类保存对象。
最典型的应用就是String类型。为了节约内存,相同的字符串将指向相同的String实例(也有例外)。复用String的存储器有个专用名字-拘留池,不过没有数量大小限制。
7. 其余
多例模式 Multition:应于单例,但从没见过哪里有标准的实现。一个类提供多个返回本身的静态属性倒很常见,如Color.Blue, Brushes.Red, 但不该该禁止别人建立新的实例。
原型模式 Prototype:把一个类实例当原型,经过复制建立新的实例。BCL中没见过,而应用开发或许偶尔用一下不足以称得上模式。由于通常像FlyWeight那样引用到那个“原型”就能够了。
Resource Acquisition Is Initialization:简称RAII。之前没据说过,看代码是C++中的模式。
二 构造模式 Structral Patterns
1. 组合模式 Composite
这种模式将某些对象由许多元件,按树状层级组合而成。应用很普遍,各类UI界面(Winform/WPF/Asp.Net Page),LINQ2XML,Lamda表达式等等,想必你们都很熟悉,很少做介绍了。
一样该模式识别性也很高,甚至不管是否有开发经验都能抽象出来,你们对树状结构太熟悉了。
可是该模式实现容易,却有许多深刻的挑战:要考虑树状结构如何最优雅地初始化,最简捷明了地增删改查。XLINQ这方面很是友好,值得借鉴,固然若是实现JQuery那种选择器就更给力了。有的树状结构,好比地理信息,还要考虑如何存储。
2. 建造者模式 Builder
传统定义:将一个复杂对象的构建与它的表示分离,使得一样的构建过程能够建立不一样的表示。
我的理解该模式作了扩展,传统定义重点是要构建的复杂对象,我理解的该模式只应关注Builder“构建者”。只要符合这样特征:有一个定义干Build流程,及组织这些流程的方式的抽象(类或接口),和将这个抽象各步流程具体实现的Builder类。
为何必须扩展对Builder模式的理解呢,缘由有三:
1.应用设计模式是为了在OCP原则下尽可能复用代码,将不变的部分抽象,对可能变化的部分,提供封装途径。
2. 若是对象复杂到须要用Builder建立,不可能地每步都产生组件。必定会有许多处理过程,虽然不产生组件,但提供注册,通信,验证,测试,存档等功能,这些过程也是能够被抽象出来定义的,但传统的Builder模式却忽略了这些“无足轻重”的过程。
要么那是一个理想化的模型,要么或许现实中真有那种建立时根本无须和外界打交道的对象(对象越复杂,可能性越小)。多是我我的经验太浅,还想象不到。
3. 既然须要在Builder中实现不产生组件的过程, 那么渐渐地,若是生产“产品”占整个业务比重变得很小甚至为0, 显然这仍然是Builder模式。
传统认为Builder必定要出“产品”,每一步产生“产品”的一个组件。然而,在抽象工厂模式中所述,工厂并不必定要有“产品”,只须包含加工处理逻辑。这里的“产品”其实指编程语言能够描述的对象,若是一个处理过程不出“产品”,并不表明没有成果,可能在修改数据库,操做文件,调用第三方API等等,这些事不能也没必要描述为“产品”对象。
3. 装饰模式 Decorator
该械通常经过装饰类的构造函数或工厂方法传入待装饰的对象,获得增长了新功能的对象。 这个模式也很好理解,我以为能够和”适配器“合并。
经常使用的例子就是DeflateStream装饰FileStream和MemoryStream。
.NET语言应该提供更好的支持,能够容许复制一个对象的属性到另外一个对象上,只要类型和名称相同。
4. 适配器模式 Adapter/Wrapper/Translator
传统定义的构造模式大都晦涩难懂,只有这个简单,就是用现有的类型,再继承一个接口,创造的新类型。为了增长功能,这种作法再天然不过,算一个模式很勉强。
我想不出更好例子:不知道你们知不知道数据集,.NET2.0就有了,如今在VS中添加新项还能选它。它会链接一个数据库,生成一些继承DataTable 的实体类集合,就是AEF的DbSet<T>同样,并实现IEnumerable<T>接口。
还有就是.NET 4.0,最经常使用的List<T>类如今实现两个新接口: IReadOnlyList<T>和IReadonlyCollection<T>,这是为了兼容WinRT。不过因为新接口的内容已经包含在实现过的IList<T>和ICollection<T>中,只是作为标记。
流行的名称叫适配器模式,不过值得注意的是BCL中有两个叫Adapter的类:ControlAdapter和DbDataAdapter,其实和该模式的没任何关系。然而,我感受这两个Adapter命名倒用得更恰当,但适配器更适合于下一个模式,叫Wrapper的话又容易和Decorator混淆,或许 Translator最好的名称。ControlAdapter实际上是下面桥接模式的一个实现,DbDataAdapter则能够算是服务模式。
一个对象若是要在List中排序,须实现IComparable接口,以适配.NET的潜规则。
5. 桥接模式 Bridge
原定义是:将抽象部分与实现部分分离,使它们均可以独立的变化。说白了,就是老鼠吃大米大象吃树叶,如今不是有转基因了吗,动物各器官都变成能够嫁接的了。 把二者的胃分离出来,使之动态变化。 我认为这种才是真正的适配器模式。
正如上面的ControlAdapter,若是咱们在DataTable加入DbDataAdapter就彻底符合该模式。
其实这个模式,还隐含这样一层意思,负责实现的这个Adapter,或者Handler,或者Operator,对象主要的业务逻辑都由其处理。否则也没必要用这种方式,可能采用别的模式了,由于对象提供修改自身逻辑的扩展方式有许多种,下面模式会涉及到
6. 代理模式 Proxy
一看到Proxy这个词,就想到用得最多就是经过VS工具自动生成的Service的代理类。还有一种状况调用COM API,咱们得本身声明一个代理方法,其实真能够也由VS自动生成。对于NET来讲,若是只是调用本域的托管资源,无须此模式。
这个模式由两部分组成:代理定义以及代理的实现机制。.NET优点是,对于应用开发,极少碰到在须要本身实现代理机制,常常咱们用了代理都没以为,好比跨域(AppDomain)调用。然而,咱们也要了解不一样代理方式的特性,好比WCF只支持DataContract的类型传输。
我有个大胆的设想,既然WCF能够整合各类网络服务,甚至还有进程通信,不知道能不能推出一种代理框架,把COM,跨域,网络的访问所有整合。
7. 泛型模式
这是一种利用.NET对泛型的强大支持,使子类型的泛型成员无须强制转换类型使用。 这很早就有人提出,我也有文章论述过。这是.NET独有的模式,虽然有人说这是一种反模式,却优雅又好用。
8. 其余
门面模式 Facade: 我实在没瞅出这怎么算模式,哪里抽象了不变,哪里封装了可变。
Front Controller:表示MVC或MVVM的架构模式,而非代码层的设计模式。
3、行为模式 Behavior Patterns
这类模式数量较多,因语言差别很大。下面列举这些经常使用模式,可能还有很多漏掉的,欢迎补充。
1. 责任链模式 Chain of responsibility
以往对这个模式没什么印象,研究代码后,感受描述了一个自动状态机。
2. 解释器模式 Interpreter
这个比较常见,我理解就是将一系列逻辑或一个对象的描述存储在一个字符串中,例如ConnectionString和格式化输出。虽然增长了出错的可能,好处嘛,就是一目了然,便于保存。像ConnectionString有工具帮忙生成,格式化输出却是比较纠结,用的时候每每要查。
3. 中介模式 Mediator
看定义,只要咱们项目的BL层根据Model,把业务封装分为不一样的helper就是中介模式。这种模式是贫血实体设计的必然方案,虽然称得上模式也很勉强,不过还有一些项目连这一点都作不到,好比会把全部业务代码几千几万行地塞进一个类里。
我的观察,中介者与桥接(适配器)区别是,后者业务实体内部含有适配器,前者没有规定;还有前者应该负责对象所有的与外部交互,后者只负责一方面。
4. 命令模式 Command
意思就是用传递不一样命令,改变系统的处理逻辑或策略,也能够说包含了Strategy模式。状态改变,策略改变的例子不少,这种命令在.NET中经常以枚举形式出现,好比LoadOptions和SaveOptions(Linq to XML) ,SearchOptions(IO),还有ConflictMode和RefreshMode(EntityFramework),好像这种枚举多以Options或Mode为后缀。
5. 策略模式 Strategy & 状态模式 State:
为何把这两种模式一块儿说呢,由于实在这两种模式没有本质区别,都是经过设置某个属性,来改变一个类处理逻辑(静态或实例都可)。但与命令模式不一样的是,这两种模式是静态的,即在改变类逻辑后,之后全部的逻辑处理都遵循这次改变,直到再次改变。而命令模式是动态的,即在调用某方法时动态传递命令参数。每次调用之间命令参数没必要相同,一次调用不影响下一次调用。
改变类逻辑的属性,若是状态模式,通常是枚举,好比Control.ClientIDMode属性;若是是策略模式,通常是委托或接口类型,好比Dictionary.Comparer属性。不过在BCL中,通常这种策略只在构造函数中定义。
值得注意的是,在多数非.NET语言中,策略模式也包含动态调用。对于.NET,这种模式部分已被优雅的委托取代,好比Linq里面许多方法,用处如此之广以至称不上一种模式了。没有委托的C++,若要传递处理逻辑或方法,要么经过指针(没语法支持),要么用此模式。Java既无委托又无指针,此模式成了生活必需品。
6. 规格模式 Specification
这种模式运用场合通常限于创建业务查询条件,是种很是优雅的模式,老赵的文章有详细论述。在我看来,算是Composite和Strategy模式的组合应用。对于非.NET来讲,实现起来就没那么舒服了。
7. 空对象模式 Null object
提供一个默认的空对象,避免对null判断,消除空引用异常,如String.Empty和Stream.Null。对于集合类,这么作颇有意义,我以为.NET能够提供更好的语法支持。对于领域实体类,好像只是让代码变得更流畅(Fluent),应该还有发展潜力,参考当一个对象什么也没干时。
我发如今ORM中,Null-Object能起到很好的做用。在加载实体时,为性能考虑咱们常不会加载其外键属性实体,但现实中这些属性是存在的,这就与代码中读取属性一个光秃秃的null造成了矛盾。使用Null Object,是一个很好的妥协,防止业务逻辑变化时致使理解错误引发bug,参考上篇。
8. 销毁模式 Dispose
这是.NET独有的模式,目的是保证GC没法回收的非托管资源,在利用完后获得释放。其方案是CLR要求咱们为须要回收非托管资源的类,实现IDispose接口,实现方式要以下(摘自《.NET框架设计》):
public class ComplexResourceHandler : IDisposable { IntPtr buffer; //非托管内存指针 SafeHandle resource; //托管资源 bool disposed; //销毁状态 public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (this.disposed) return; if (disposing) //有条件释放托管资源 { resource.Dispose(); } Marshal.FreeHGlobal(buffer); //保证释放非托管资源(简化省略了判断) disposed = true; } ~ComplexResourceHandler() { Dispose(false); } }
这样即便不使用using和try-finally语法,也能保证在GC回收对象时清理掉这些资源。 园子里的铁哥,应该常与之打交道。这些类从构造上说,其实符合适配模式。另外,按规范访问已经dispose的对象,应该抛出ObjectDisposedException。这些搞得这个模式有点繁琐无趣,.NET若是能增长更强力的语法特性就行了。
9. 试行模式 Try-Parse
对于.NET来讲,在正常业务操做失败时,抛出适当的异常是一种规范作法。虽然有一点性能负担,但能使代码更易维护。
然而某些状况下对特别频繁一些操做,则采用这种模式避免抛出异常而影响性能,将转化结果传递给参数,将成功与否做返回值。 经常使用的如Int32.TryParse(string s, out int result)和Dictionary.TryGet(T key, out K value)。
对于不支持异常的如C语言等,这是最普通不过的作法了。能够再次看出设计模式与语言特性息息相关。
10. 自选模式 Optional feature
这也是《.NET框架设计》提到的模式,中文名是我本身起的。主要用于框架开发。书上举了Stream的例子,继承Stream的类有许多,特性各异。有的可读写可定位,如MemoryStream,NetworkStream只读,如HttpOutputStream只能写。通常来讲,设计模式都是尽量地将变化抽象出来。对于某些复杂业务的类如Stream,若是增长IRead/IWrite/ISeek,太多抽象会致使易用性下降,实际开发中又无API须要这些接口适配,且还没法保证这些接口只被用于修饰Stream。因而就有这种模式,在一个类中为所有业务操做提供可重写的默认实现,并可查询这些操做是否在子类中获得支持。
能够比较一下使用IRead接口和可选特性的调用方式:
static void Process(Stream stream, byte[] buffer) { //使用IRead接口 var readImp = stream as IRead; if (readImp != null) { var firstByte = readImp.Read(buffer, 0, buffer.Length); } //使用Optional feature if (stream.CanRead) { var firstByte = stream.Read(buffer, 0, buffer.Length); } }
对于框架和应用总体系统而言,这是一种反模式。然而对应用开发而言,提升了易用性,下降了复杂性。注意到.NET4.5实现了支持async的一系列方法,这种模式更显得方便。好比一个小超市,虽然购销管理效率不及专门的菜市场和五金店,对于顾客却很是便利。
其余
服务者模式 Servant:例StringComparer。相似桥接模式中的桥,或者Adapter。跟中介者模式也很难区分。
访问者模式 Visitor: 这实际上是策略模式的动态变种。Visitor是业务逻辑类,操做共同的一类对象。不过在.NET中,如前所述,已经没有什么特别之处,也称不上模式了。
迭代器模式 Iterator: 被foreach和yield取代
观察者模式 Observer:已经被事件机制取代。
备忘录模式 Memento:这虽然是一种不错的解决方案,但是应用场合很稀有,好像没太大意义记住。
模板方法 Template Method:只是面向对象继承的基本特性。
把主要的设计模式一网打尽地笼统归纳了下,但愿能帮助你们再也不困惑于.NET的设计模式,也但愿你们能多提出意见。