手绘设计模式结构图

GoF的设计模式一共23个,可以分为3大类:创建型、结构型和行为型,这篇文章主要讨论创建型。

创建型的设计模式包括:简单工厂(Simple Factory)、工厂方法(Factory Method)、抽象工厂(Abstract Factory)、单例(Singleton)、构造者(Builder)和原型(Prototype),我们分别来讨论。

我们首先来看工厂系列的3个设计模式,它们都主要是针对软件设计中的“开放-封闭”原则,即程序应该对扩展开放,对修改封闭。特别是当我们的程序采用XML+反射的方式来创建对象时,工厂模式的威力就完全展现出来了,这时我们可以通过维护配置文件的方式,来控制程序的逻辑。

1)简单工厂,当我们的程序在实例化对象时,如果输入条件不一样,产生的对象也不一样,那么我们可以考虑使用简单工厂对不同的实例进行统一封装, UML结构如下:

优点:封装了具体对象的实例化过程,Client端和具体对象解耦,同时ProductManager可以作成静态类或者Singleton对象,然后可以使用HashMap缓存具体对象(前提是对象没有时间依赖性),降低创建对象的次数。

缺点:当增添一种新类型的对象时,需要修改Productmanager的代码(如果不采用XML)

2)工厂方法,它是针对简单工厂的改进版,添加了对ProductManager的抽象,UML结构如下:

优点:结构更加灵活,对于某种类型的对象来说,会有一个特定的对象工厂指向它,这样当我们需要添加一种新类型的产品时,只需要添加两个类,一个是具体产品类,一个是新产品的工厂类。这样更加灵活。

缺点:结构开始变得复杂,而且最终还是需要Client端来确定究竟使用哪一个Factory(当然这个信息可以保存在上下文或者配置文件中)。

3)抽象工厂,这个是最复杂的工厂模式,它用来生成一个产品线上的所有产品,我们假设一个产品线上包括多个产品,不同的产品线上的产品个数是一样的,这样我们需要一个针对产品线的抽象,并且很显然不同产品线上的产品是不可能混到一起的。对应的UML结构图如下:

上图表明,一个产品线上的产品由IProduct1和IProduct2组成,客户端在获取产品时,这两个产品应该是同时返回的,因此对于IProductManager来说,它需要同时生成这两个对象。

优点:对创建产品家族的行为高度抽象,添加一个产品线的逻辑比较清晰。

缺点:当我们对产品线上的产品进行增加和删除时,对应的操作比较麻烦,所有的产品工厂都需要进行修改。

4)单例,这是比较好理解的一个模式,从字面上说,就是程序在运行的过程中,希望在任意时刻,都只保留某个对象的唯一实例。对应的UML结构图如下:

单例的实现方式一般包括几步:1)私有的指向自身的字段;2)私有构造函数;3)公开对私有字段进行实例化的方法。也有几种针对具体语言进行的改善,例如针对多线程采用double lock机制,采用常量方式定义私有字段、使用内嵌类来实例化字段等。

我们也可以对单例进行一些适当的扩展,例如我们将对象的个数由1个变为N个,这就成了对象池。

通常工厂模式中会使用到单例模式,特别是对于简单工厂来说。

5)构造者,对于一些复杂对象来说,它可以分成多个不同的部分,在实例化时,不同部分之间实例化的顺序,有时会有严格的限制,这时我们就可以使用构造者模式了。对应的UML结构图如下:

我们定义了IBuilder接口来实例化对应的不同部分,同时有一个方法来返回对象的实例。而Constructor类的Construct方法会按照业务逻辑依次调用实例化部分对象的方法,即BuildPartA、BuildPartB,这里的调用顺序,完全由业务逻辑来控制,最后可以调用GetProduct方法取得完整的对象实例。

我们有时也会对上图进行修改,例如将GetProduct放到Constructor中,或者将Construct方法放入到GetProduct(取消Constructor)中。即使有这些变形,但是基本的思想是不变的。

6)原型,我们在程序运行过程中,当需要有新的实例对象时,有时并不希望是从头创建一个对象,而是希望新的实例的状态和某个已存在的实例保持一致,这就是原型模式发挥作用的地方。对应的UML结构图如下:

在.NET中,已经定义了IClonable接口来实现原型模式。需要注意在实现时,会有深拷贝和浅拷贝的区别,深拷贝会同时拷贝堆栈和堆上的内容,而浅拷贝只会拷贝堆栈上的内容。

在这部分里,我们关注GoF里面的结构型模式,它主要是用于描述如何将类组合在一起去构成更大的结构。结构型模式包括适配器(Adapter)、装饰(Decorator)、桥接器(Bridge)、享元(FlyWeight)、门面(Facade)、合成(Composite)以及代理(Proxy)模式。

下面我们对上面提到的模式分别进行描述。

1)适配器(Adapter)。当我们已经开发出一个模块,有一套清晰的接口,并且模块正在被某个功能使用(意味着模块接口改变的可能性不高),这是如果有另外一个功能也需要使用这个模块的功能,但是对应的是一套完全不同的接口,这时适配器就可以发挥作用了。

适配器模式分为两种,一种是对象适配器,一种是类适配器,对象适配器的UML图如下:

这里Adaptee1和Adaptee2指两套不同的子系统,它们作为Adapter的属性存在,可以使用IoC的方式指定。

类适配器的UML图如下:

同样是两个不同的子系统,但是这里我们创建了2个Adapter类来分别指向两个子系统。在这里我们可以在Client和ITarget之间,设置一个Adapter工厂,来根据业务需求创建不同的Adpater实例。

2)装饰(Decorator),假如我们已经开发了一套功能,然后根据需求,需要增加一些子功能,而且这些子功能是比较分散比较时可以增删的,这时如果直接修改接口,那么会造成接口功能复杂并且不稳定,针对这种情况,我们可以使用装饰模式。对应的UML图如下:

上图中,ConcreteComponent已经实现了Component的基本功能,对于一些附加的功能,如果放在ConcreteComponent中不合适的话,我们可以像ConcreteDecoratorA一样,创建一个基于Decorator的类,通过SetComponent方法将核心功能和辅助功能串在一起。

有时,为了简单,我们也可以把ConcreteDecorator直接挂在Concretecomponent下面。

3)桥接器(Bridge),面向对象提倡的几个最佳实践包括:1)封装变化;2)面向接口编程;3)组合优于继承;4)类的职责尽量单一。桥接器完美的体现了这些,通过创建型模式,我们可以很好地达到面向接口编程的目标,也就是说我们在程序中各变量的声明类型是接口类型或者抽象类,而具体的实现类型则由不同的设计模式使用不同方式指定。这在接口或者抽象类基本稳定的情况下,是很好地,但当接口需要发生变化时,我们如何去处理?可以看看桥接器的UML图:

通过这个图,我们可以看出,Implementor接口的变化,对于Client来说,基本是没有影响的。Abstraction会持有Implementor的一个实例。

4)享元(FlyWeight),当我们系统中需要使用大量的小对象,但我们又不希望将所有的小对象都创建出来时,可以考虑使用享元模式,它会抽取小对象中的公共部分,将其封装为基类,然后针对不同条件创建小对象,同时在对象池中维护这些小对象,客户在需要使用小对象时,首先在对象池中查找,如果存在,直接返回。对于小对象中“个性”的部分,由调用小对象的客户端进行维护。对应的UML图如下:

除了上述的简单享元,还存在一种复合享元,对应的UML图如下:

图中,CompositeConcreteComponent是不共享的,但是它里面包含很多简单的享元,这些享元是共享的,我们可以把它想象成一个特殊的“享元工厂”。

通常提到享元,最常见的例子就是文本编辑器中的26个字母,在.NET中,字符串常量也使用了享元模式。

在享元模式中,我们通常会将FlyWeightFactory设计为单例模式,否则享元就没有意义了。

5)门面(Facade),如果我们的程序需要深入调用某个模块的内部,但我们又不想和模块过紧耦合,这时可以考虑使用门面模式,来对外部封装内部子系统的实现。简单的门面可能和代理在某种程度上很相似。

门面模式没有固定的UML图,它是根据客户端的实际需求以及子系统内部的接口来确定的。

6)合成(Composite),当我们的对象结构中存在“父子”关系时,可以考虑使用合成模式。它分为两种,一种是安全型的合成模式,UML图如下:

这种类型的合成模式,对于Component的增、删、改,都在Composite中维护,Leaf根本不知道这些操作。另一种是透明型的合成模式,UML图如下:

这种类型的合成模式,自上而下所有的Component都会有增、删、改的操作,只不过对于Leaf来说,这些操作时没有意义的。

7)代理(Proxy),在编写程序时,有时我们希望使用某个对象或者模块的功能,但是因为种种原因,我们不能直接访问,这时就可以考虑使用代理,对应的UML图如下:

需要注意的是,在这里RealSubject只有一个,如果有多个,那么就是Adapter了。另外,代理也可以加入自己的一些逻辑处理,例如PreExecute和PostExecute。如果这里有多个Proxy,那么就是Decorator了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容

  • 设计模式汇总 一、基础知识 1. 设计模式概述 定义:设计模式(Design Pattern)是一套被反复使用、多...
    MinoyJet阅读 3,875评论 1 15
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,497评论 18 399
  • 一、设计模式的分类 总体来说设计模式分为三大类: 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者...
    RamboLI阅读 727评论 0 1
  • 一个UML类图 类之间的关系 类的继承结构表现在UML中为:泛化(generalize)与实现(realize) ...
    僚机KK阅读 631评论 0 0
  • 本文首发于个人博客:Lam's Blog - 谈谈23种设计模式在Android源码及项目中的应用,文章由Mark...
    格子林ll阅读 4,603评论 1 105