设计模式(三)——结构型模式如何搭建健壮且更好维护的系统

本文长约3813字,预计阅读时间10分钟。
本文为我在学习设计模式时的学习笔记
上一篇:创建型模式 https://www.cnblogs.com/neumy/p/14586159.html

结构型模式

结构型模式规范的主要对象在于 如何将类和对象进行组织以形成更大的结构。从组织的方式上进行划分,分为 类结构型模式对象结构型模式

类结构型模式采用继承的方式来组织接口和类,主要包括

对象组织模式采用组合或聚合(在合成复用原则中有介绍)的方式来组织对象,主要包括

代理模式(Proxy)

代理模式主要为了解决一个纠结的问题:如何在不适合直接引用一个对象的情况下访问一个对象。这听起来是一个很憨的问题,为什么访问一个对象却不直接引用它?

事实上只需要稍微思考一下便能得出一个场景:假如一个数据库对象,同时提供了增删改查的方法,此时对于一个用户,其需要访问数据库,但是显然如果将数据库完全暴露给用户是极其危险的,我们只应该暴露其需要的方法(比如增),同时也要限制其行为(比如只允许上传一定大小的文件)。因此我们需要创建一个代理类,在代理类中接受用户的请求并进行判断,然后再操作数据库,构成一个第三方类。

代理模式基于迪米特法则的思想,在结构上设计了三个主要角色:

  1. 抽象主题:通过接口或抽象类来声明一个真实主题和代理对象的方法。可以理解为描述真实对象的行为,并作为父类接收代理类的继承从而使其共享一套方法。注意该类描述了真实主题和代理类两者共同的面向用户所应该开放的接口
  2. 真实主题:我们希望访问但是不能直接引用的对象,继承自抽象主题。
  3. 代理类同样继承自抽象主题并进行一定的拓展(比如权限判断等),其包含一个指向真实主题的引用,通过和真实主题共享一套方法从而实现代理模式对用户的透明,即使得用户以为自己在使用真实主题。

同时,代理模式还提出了 动态代理的概念,其为了解决一个真实主题必须对应一个代理类的问题(因为代理类内含有对真实主题的引用)。总体来说就是使得一个代理类能够同时代理不同的基于同一接口实现的真实主题,而该代理类实际上是一个带构造器的接口,因此能够new出代理类的实例出来。

适配器模式(Adapter)

适配器模式规范的是如何让多个互不兼容的类进行合作。由于接口不统一,使得两个在逻辑上具有耦合性的类无法在实际实现中进行沟通,此时需要利用一个第三方的Adapter类来进行适配。

适配器模式既可以通过继承(类结构型),也可以通过组合或聚合(对象结构型)实现,前者类之间耦合度高且对程序员要求较高,使用较少。

适配器模式下,调用者应该能够调用目标所拥有的接口而无法察觉到其实调用的是适配器对象,使得适配器对象对调用者透明。同时也应当复用现有的类以使得适配器能够具有更好的复用性。注意,目标类和适配器类之间必须要解耦,这是因为适配器类通常不针对特定目标类使用,可能适用于多种目标类,从而被赋予复用性。

值得注意的时,如果你使用的类都是自己编写的,那么通常来说是不会使用适配器类的(除非业务需求发生很大变化或者需要进行多场景的兼容)。注意不要使用过多的适配器,否则会导致代码过于复杂。

实现上,适配器模式主要有以下几个角色:

  1. 目标接口:一个使用者所看到的接口,其接口符合业务需求。
  2. 适配者:被访问和适配的现存组件库中的组件接口,其接口与业务需求不一致,需要进行适配,即上文提到的目标类。
  3. 适配器:实现(implement)了目标接口。作为一个转换器,通过引用一个适配者对象(对象结构类型)或继承适配者类(类结构类型),使得使用者能够以访问目标接口的形式访问适配者。

但是和代理模式不同的是,适配器并不是在适配者的基础上做了拓展(或限制),而是改变了它的接口以适用于当前业务场景。

桥接模式(Bridge)

桥接模式主要解决的问题在于 如何处理一个可能拥有多维度变化的类。这是什么意思呢?考虑一款手机,其颜色、大小、价格都可能变化,因此是多维度的。如果我们设计一个手机类,可能会以属性的方式去保存这些信息。我们会发现,为类设置成员变量实际上就是一种组合或聚合的操作,而桥接模式的核心也在于组合。

桥接模式主要是为了“将抽象和实现分离,使其独立变化”。这句话也许很难理解,那么假如手机类有一个芯片属性,其根据手机的类型不同而可能发生变化,那么我们将芯片单独设计为一个接口,并在手机类中引用它。这样的话,芯片的具体实现就同“手机具有一个芯片”这一抽象相分离,桥接模式正是面向这种直观的设计理念提出了规范。

实现上,其具有几个角色:

  1. 抽象化角色:一个抽象类,并实现对实现化对象的应用。比如手机类。
  2. 拓展抽象化角色:具体实现抽象化角色,比如iPhone。
  3. 实现化角色:一个接口,用于定义实现化角色。注意即使名字是实现化但是实际上还是抽象的接口。比如芯片抽象类。
  4. 具体实现化角色:实现实现化角色。比如A12芯片。

可以看见,拓展抽象化角色同具体实现化角色分离,通过组合的方式实现了两者的交互。

装饰器模式(Decorator)

装饰器模式期望解决的问题是 如何在不改变现有对象的结构的情况下为其增加额外功能,而分类上属于对象结构型模式(即采用组合或聚合方式)。

通常我们通过继承来实现对一个类的拓展,而装饰器通过组合关系来包装一个读写,从而能够实现“即插即用”的拓展,不改变原有对象且动态添加功能。

实现上,主要包括以下几个角色:

  1. 抽象构件:定义抽象接口,规范定义一个准备接收附加责任的对象。
  2. 具体构件:实现抽象构件,即目标类。
  3. 抽象装饰:继承抽象构件,并包含包含一个具体构件对象的引用,使其能够通过子类拓展目标类。
  4. 具体装饰:实现抽象装饰并为具体类添加附加功能。

注意,其一,抽象装饰和具体构件同时继承同一个父类,而抽象装饰包含向具体构件的引用,可以说其是具体构件的一个包装,通过调用内含的具体构件对象而实现和具体构件一模一样的功能。其二,功能的拓展在具体装饰中完成而不是在抽象装饰中完成

需要注意其于代理模式的区别。代理者理论上在编译时就确定了被代理者实例,而且经常用于限制访问,也就是削弱被代理者。而装饰者通常需要传入一个被装饰者对象来确定装饰的具体是哪个实例,而且经常用于增加功能,也就是增加被装饰者。

外观模式(Facade)

外观模式为了解决当一个系统的功能很强导致有多个方法调用这个系统,导致访问变得复杂时, 需要对系统的访问进行拆分以为不同的访问提供统一的接口

外观模式通过外观类来实现对多个子系统的包装。由于可装饰器模式一样通过持有引用来实现,因此也是对象类型模式。

外观模式下具有这样几个角色:

  1. 外观角色:定义系统对外的共同接口,包含对多个子系统角色的引用,并提供包装好的方法,使得客户角色能够在不了解具体调用了哪些子系统的情况下实现其所需功能。
  2. 子系统角色:作为系统的部分功能而接受外观角色的调用
  3. 客户角色:通过外观角色来访问子系统

外观角色的设计是外观模式的重中之重,它需要同时完成整合各子系统功能、考虑各子系统需求的工作,同时新的子系统增加可能影响外观角色的设计。

享元模式(Flyweight)

享元模式用于解决 大量相似对象导致了大量存储相同信息的内存空间,通过共享这些部分来实现系统资源的节约。运用共享技术来支持大量细粒度对象的复用。享元模式需要两个要求:细粒度和共享对象。前者使得对象相似且数量巨大,后者使得相似部分能够被共享。比如excel中的单元格就属于细粒度对象,其大小、背景色等属性能够共享,而其内容不能共享。

享元模式下会导致无法共享的部分被外部化,使得耦合性存在一定强度的降低。

在实现上,享元模式的主要角色包括:

  1. 抽象享元模式:作为具体享元类的基类,实现 享元的公共接口非享元的外部状态以参数的形式通过方法传入。比如描述excel的单元格的一个抽象。
  2. 具体享元:具体的享元,比如不同属性的单元格。
  3. 非享元类:不可共享的外部状态,比如excel单元格的内容
  4. 享元工厂,用于创建&管理享元角色。当用户请求一个享元对象时,先检查是否已经有符合要求的享元对象存在,有则返回它,否则进行创建。这是和工厂模式的核心不同点,工厂模式会直接创建。

享元工厂就是用来保存和管理共享部分的主要类,而不同的共享部分被存放在具体享元类中,享元工厂保存不同的具体享元对象以待用户调用、用户调用后获取到具体享元对象。而由于享元模式需要保证每个用户即使拿到的是同一个具体享元对象,仍然不应该相互影响,故享元对象的不可共享状态是存储在享元对象之外的非享元类中的,使用具体享元对象方法时需要传入非享元类

如果你还记得工厂模式的话,其实会发现很像。只是工厂模式不会考虑共享问题,而是直接创建+返回,而享元模式面向资源共享而进行了改进,规范了如何创建和管理细粒度对象。

组合模式(Composite Pattern)

组合模式用于规范“部分-整体”的结构,比如部门-分公司-公司结构。其将对象组合成树状的层次并规范了访问方式

注意在树结构中,根节点和树枝节点属于同一种类型,叶子节点通常在语义上不与树枝节点相同。而在组合模式中,我们将根节点、树枝节点和叶子节点视作同种类型并使用统一接口!。这样的设计使得用户可以无需分辨是否是叶子节点而直接进行操作,提高实用性。

组合模式的优点在于使得用户能够不顾类型地操作所有层次对象,同时也使得整体结构的控制、增删非常方便。

实现上,组合模式包含:

  1. 抽象构件:主要作用在于为节点声明公共接口,并实现默认行为。
  2. 树叶构件:没有子节点,用于继承和实现抽象构件
  3. 树枝构件:继承和实现抽象构件的同事,更主要的功能在于存储和管理子部件,通常有add()等增删改查方法。

组合模式有两个方式,透明式和安全式

  • 透明式:抽象构件声明了所有子类的所有方法,这使得用户能够无差别地使用所有节点。但是这样的话必须要在抽象构件声明增删改查方法,导致树叶节点必须对这些方法进行处理(不安全因素)。
  • 组合式:把增删改查方法放到树枝节点中实现,但是这样会导致用户无法对叶子节点调用增删改查,使得透明性丧失。
posted @ 2021-03-31 15:26  neumy  阅读(91)  评论(0编辑  收藏  举报