系统构成与系统结构
系统构成
系统的构成结构、主要元素有层(layer)与 区(partition),层是按照抽象层次进行的水平划分、是经过抽象提取出来的,高层建立与低层之上(单向依赖);而区是同一层中的垂直划分(同层的区是双向合作关系);进入区里面再到包(package层次),最后具体到类(class)
结构型
系统结构类型相关的设计模式的一大关注点是其是否利用继承层级的类结构和聚合层级的对象结构在上面提到的类层级下构建了更高层的抽象 or 更大的复合结构(另一个关注点就是其对于GRASP和SOLID等设计原则的遵循)
前言
请先确保能读懂UML类图。UML类图基础 可参考
结构型的设计模式
桥梁模式(bridge design pattern)
- 应用场景:若某个类存在两个独立的变化维度,桥梁模式将其分解为两个独立的等级结构,并且通过一个“桥”关联起来,使其两个维度可以独立变化的同时又通过“桥”连接到一起(从而组合使用功能)
- 应用举例:
- 跨平台播放器:
- 假设处理流程是
- 通过调用特定视频格式的信息提取代码来提取信息
- 通过调用特定平台的播放组件并且接受提取出来的信息来播放
- 如果是采用继承模式就需要写a * b个类(如window-AVI处理类,mac-AVI处理类…),
- 采用桥接模式后改成a + b个类(避免了子类爆炸),步骤上分别委托到各自中去执行,且各自的代码实现独立变化
- 假设处理流程是
- 跨平台播放器:
- 图示
- 代码示例
1
2
3avi_parser = AviParser()
player = WindowsPlayer(aviparser, "xxx.avi")
player.play() - 优势
- 客户端可以针对两个维度的抽象层编程,在程序运行时再动态确定两个维度的子类,动态组合对象,将两个独立变化的维度完全解耦,以便能够灵活地扩充任一维度而对另一维度不造成任何影响
- 基于组合来避免了在此种情景中基于继承的子类爆炸问题
- 同理、可以推导到多维
适配器模式(adapter design pattern)
- 应用场景:接口转换器,用于解决已有的服务提供者与服务使用者之前的接口不兼容的问题
- 两种实现方式:(*下面用到的单词解释:Target表示要转化成的接口定义,Adaptee是不兼容ITarget接口定义的接口,Adaptor将Adaptee转化为一组符合Target定义的接口)
- 类适配器:基于继承关系实现(优势:若Target与Adaptee有部分一致的接口就可以不用再在Adapter中实现了)
- 对象适配器:基于组合关系实现(优势:组合结构本身比继承结构更灵活、更拥抱变化)
- 类适配器:基于继承关系实现(优势:若Target与Adaptee有部分一致的接口就可以不用再在Adapter中实现了)
- 用户视角:调用适配器类进行访问
- 在设计原则上、其表现了:
- 保变原则:将容易变化的地方(调用方式)独立出来与核心实现解藕
- 开闭原则:对扩张开放、对修改封闭。这里就是通过适配器来扩展代码适用范围
- 纯虚构原则:将一组高内聚的职责分配给一个虚构的或处理方便的“行为”类,它并不是问题域中的概念,而是虚构的事物,以达到支持(这里的Target、Adaptee之间的)高内聚、低耦合和复用。(这里的Adapter就是此虚构的行为类)
装饰模式(decorator design pattern)
- 应用场景:主要用于灵活的对原始类进行可组合的功能增强,也避免了如果要基于继承实现同样效果的子类膨胀问题
- 举例说明:一个显示组件类Widget,其可开/闭的选项有 加边框、滚动条、可拖动、可缩放…
- 继承实现:如ScrollableWidget、ZoomableWidget…,最后甚至有BorderedScrollableDraggableWidget类 -> 子类膨胀
- 装饰者实现:见下面类图
- 若要实现上面的BorderedScrollableDraggableWidget 可通过new DragDecorator(new ScrollDecorator(new BorderDecorator(someWidget)))实现
- 这里类图需要注意的是WidgetDecorator既继承了Widget、又由Widget聚合而成。
- 继承:为了保持装饰后的类型与未装饰是一致的,从而保持透明性避免引起别的使用地方可能出现的类型问题 & 同时也保证了嵌套时的输入类型一直都是widget类型(否则不可嵌套)
- 聚合:是从某种意义上、其只是widget的组装者,但是也可以看作是其包含了widget、从而是聚合关系
- 用户视角:可同时访问装饰类与实现类、并且自己组合选择装饰类来对实现类进行“装饰”
- 注意点:
- 装饰者模式可嵌套、也就是可以动态的增加/减少,这也是继承办不到的
- 装饰装饰、顾名思义是与“核心”无关的东西,如这里someWidget自己内部的核心逻辑是完全隔离的,装饰模式只应该提供辅助性/边缘性的职责。同时这也是符合关注点分离原则(对于想改变核心的情况、应该使用策略模式)
- 构造了更大的复合结构
代理模式 (proxy design pattern)
- 应用场景:对原始类的访问控制(组合 + 选择性委托),功能增强(继承 + 扩展)
- 举例说明:如防火墙、C++的智能指针、写时拷贝
- 用户视角:调用代理类进行创建,原始类不再能/推荐被使用
- 对比下装饰器模式
- 代理模式非返回原始类型 & 不可嵌套的
- 用于替代原始类而不是装饰原始类
门面/外观模式(facade design pattern)
- 应用场景:一般用于写类库时,考虑使用者在基于此类库实现某功能时,是需要能相对层级浅、数量少的引入,or 需要数量多、层级深的引入
- 图示说明:
- 使用优势
- 用户使用的心智负担小
- 子系统各个服务类若发生变化不同意影响到用户(假设最原始的就是直接引入此类了,而后期又对此类进行了改名等变化)
- 对比适配器模式,外观模式构造了一个抽象层次更高的类,其子系统是实现细节,而外界使用者不再关心实现细节、而是直接通过Facade类即可完成使用。而适配器模式是平层次的
组合/复合模式(composite design pattern)
- 应用场景:特别指定应用于能够表示为树形结构、且叶子结点可以是不同类型但是一定有相同的抽象操作方法/属性的情景(树形结构提供了一种遍历所有节点的方式以及层级关系)。在这种场景下使用组合模式来构建操作一致性(从复合对象作为入口后可不用关心具体对象,直接调用复合对象提供的方式/属性即可达到全部操作的目的)
- 举例:文件系统里面的文件与目录(复合类型是文件系统,具体类型是文件/目录),公司结构中的员工与部门(复合类型是公司,具体类型是员工/部门)
- 两种类型:
- 透明型: 复合类型暴露的方法是具体类型方法的合集
- 安全型:复合类型暴露的方法是具体类型方法的交集
- 透明型: 复合类型暴露的方法是具体类型方法的合集
- 一个具体的例子:假设一个页面有各种各样的前端组件(此时复合类型是front_page),而此复合类型下面可以有各种具体的组件类型(假设有Container、Button、Text、……)
- 假设对于一个已经渲染好的页面,用户突然要缩小显示为90%,则首先:基于树形结构,以复合类为入口能够简单滴遍历到各个叶子的具体类,然后复合类又规范了一个假设名字叫zoom的方法,则我们是知道叶子类都有此方法,故遍历到的时候就不需要考虑类型直接调用即可(当然,如上面安全型表示的,有非交集的方法,这种调用就只利用了方便的遍历,具体能不能调用还要再做判断)
- 再看看上面的例子,集合类是个实现类,叶子节点都是继承/泛化它,所以继承带来的好处我们都能够方便的用上(比如渲染前一开始就在复合类上设置了能有border的组件都要默认显示border的标示,则后面的叶子节点就不用设置直接能访问到了)
- 构造了更大的复合结构
享元模式(flyweight design pattern)
- 23个设计模式中唯一一个针对性能而不是设计的模式
- 应用场景:针对一类粒度小、数量多、相识度高、种类有限、具有值语义的对象,通过共享其内在相同部分来复用对象、节省内存,节约创建,共同部分之外的上下文才是每个对象自己独有的、需要维护的
- 备注 - 值语义:目标对象由源对象拷贝生成,且生成后与源对象完全无关,彼此独立存在,改变互不影响
- 实现的注意点:
- 核心是抽象出一类对象内在的、不因环境而异的状态,封装后作为共享单元(flyweight)
- 由于要共享,所以不能每次由客户自己亲自创建对象,一般是通过工厂模式进行创建
- 举例:一个象棋游戏厅,房间上千万,但是象棋的类型(相、车、马…)、颜色是一一对应且很少的,如果每个房间都初始化自己的多有棋子对象,就会有太多重复。从而可以将类型和颜色作为享元,位置作为外部上下文从而节约大量的内存
上面的几种设计模式小节
- 桥梁模式:将一个类多个维度的变化拆解为单独的类并且通过组合作为桥来使用,分离变化
- 适配器模式:解决已有服务提供者与服务使用者之前的接口不兼容的问题
- 装饰模式:灵活的对原始类进行可组合/撤销的功能增强
- 代理模式:对原始类的 访问控制(组合 + 选择性委托),功能增强(继承 + 扩展)
- 门面模式:提供了粗粒度、抽象层次更高的接口,以简化/统一外部对子系统的访问/使用,也隔离了变化
- 复合模式:抽象理解和统一管理不同的类型
- 享元模式:减少对象创建 + 节约内存