前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手绘设计模式结构图

手绘设计模式结构图

作者头像
爱因斯坦福
发布2018-09-10 16:44:41
1K0
发布2018-09-10 16:44:41
举报
文章被收录于专栏:KK的小酒馆KK的小酒馆

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了。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016.10.10 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档