体系结构设计整理
体系结构设计整理
一、 名词解释
1、 软件体系结构概念(3点)
1、 高层结构
组成部分:部件(Component )、连接件(Connector )、配置(Configuration ) 部件聚集了软件运算与状态,连接件聚集了部件之间的关系
部件:在软件的体系架构中封装了数据及其处理操作的元素,提供具体应用服务,定义如下:
部件是具有如下特征的架构实体:
1) 封装了系统中的功能和/或数据的一个子集
2) 通过清晰定义的接口来限制外界对所封装的子集的访问 3) 对于被要求执行的上下文有定义明确的依赖关系 部件要素:Name 、Property 、Port Ch3 PPT P17
连接件:在复杂系统中,交互会比部件范围内的功能实现更重要且更具挑战性,提供独立交互的方法,连接件定义如下:
1) 连接件是负责引起和约束部件之间交互的构件
2) 它们起到连接作用,但却不是被连接的对象,只是提供连接的规则 Ch3 PPT P24
配置:在系统架构中,部件与连接件之间的一个特殊联系的集合,部件与连接件在此特定的组合方式下相互协作完成特定的目标 2、 关注点
软件体系结构对这些关注点进行权衡的过程起到了交流媒介的作用
系统质量属性:可靠性、可修改性、性能、安全性、可测试性、可用性 项目环境:
1) 开发:人员技术水平、成本、上市时间、资源
2) 业务:收益、系统生命周期、市场定位、首次发布日程 3) 技术:开发平台、硬件设备、开发工具、模型和标准 业务目标 3、 设计决策
一个系统的体系架构是有关系统的一系列重要设计决策的集合, 体系结构也是一系列对系统设计所做的设计决策,包含了重要的“设计决策”,它们说明了软件体系结构得以形成的“理由”,会指导详细设计、实现等后续软件开发工作 设计决策的过程:问题->候选设计->理由->解决方案 设计决策的重要性:
1、 设计决策相互影响,一旦确定便难以改变
2、 在确定设计决策过程中,极易违背设计规则和约束
3、 之前废弃的决策难以去除、仍然会影响后来的决策
2、4+1View
即逻辑视图、开发视图、进程视图、部署视图 + 用例视图,前四个为体系结构视图,后一个为需求视图
1) 场景视图(Scenarios):
定义:关注系统最为重要的需求,描述系统应该实现的场景与用例
作用:它们一方面说明软件体系结构设计的出发点,驱动其他4个视图的设计,另一方面用于验证和评估其他4个视图的设计,保证它们的正确性。用例视图位于4+1视图的中心,被其他4个视图环绕
描述:可以用UML 的用例图进行描述,其重点在于对用力场景的描述 2) 逻辑视图Logical view:
定义:关注系统的逻辑结构和重要的设计机制,描述系统提供的功能和服务 定义:解释系统的逻辑结构和重要的设计机制,其主要内容是软件体系结构的抽象规格,主要关注点是满足用户的各项需求,尤其是功能需求,质量属性需求和约束 描述:部件类型用构造型《component 》扩展了的类来描述
连接件类型用构造型《connector 》扩展了的类来描述 特征用构造性《property 》扩展了的类来描述
3) 开发视图Development view:
定义:关注系统的实现结构,描述系统的开发组织
描述:利用UML 中的构造型《process 》扩展的主动类描述 4) 进程视图Process view:
定义:关注软件体系结构的运行时表现,描述系统的并发进程组织 描述:利用UML 中的构造型《process 》扩展的主动类描述 5) 部署视图Deployment view:
定义:关注系统的基础设施,描述系统的部署于分布 描述:使用UML 中的部署图描述
3、体系结构设计决策
一个系统的体系架构是有关系统的一系列重要设计决策的集合
定义:设计决策是指决定策略与办法。是对元素、特征和处理的选择,它们涉及一个活多个关注点,直接或间接的影响到软件体系结构。
设计决策核心的知识可以分为四个部分:关注点,解决方案,策略和理由。 设计决策的重要性:
1、 设计决策相互影响,一旦确定便难以改变,常见的设计决策间影响有促进、冲
突、禁止、包含、从属、依赖等。
2、 在确定设计决策过程中,极易违背设计规则和约束
3、 之前废弃的决策难以去除、仍然会影响后来的决策,而且该影响是不可逆的,
即很难消除该影响。
软件体系结构的设计决策是一个持续的过程,每个决策都要在其前面设计决策的基础上进行,要符合前面设计决策所规定的的设计规则和约束,解决自己的特定问题和关注点。但是所有设计决策都要遵守概念完整性,保证所有设计决策之间相互协调并且与整个系统目标相协调。
4、GRASP 模式
GRASP ,即通用的职责分配软件模式(General Responsibility Assignment Software Patterns )包含以下内容:
1) 低耦合:分配职责时保证低耦合,即降低依赖并增加复用性
2) 高内聚:将复杂度控制在可管理范围之内。不可能完全消除时序内聚、过程内聚、通信内聚,必须将这些内聚的 拥有者变成转发者。一个对象仅实现单一的职责,而不实现复杂的职责组合
3) 专家模式:信息的封装。分配职责要看对象所拥有的信息,谁拥有哪方面的信息,谁就负责哪方面的职责,并且一个对象只做这一件事情
4) 创建者模式:如果某个对象与其他对象已经有聚合/包含关系,则这个对象应该由聚合/包含它的对象来创建。这样就不需要依赖第三方,不会增加新的耦合。 如果符合下面的一个或多个条件,创建A 类的职责将会分配给B 类: B 类聚合A 类的对象 B 类包含A 类的对象 B 类记录A 类对象的实例 B 类密切使用A 类对象
B 类包含A 类初始化所需信息 5) 控制者模式:对象协作设计中的一种风格,解决“处理系统事件”这一职责的分配问题。 将处理系统时间信息的职责分配个代表其中一个选择的类 1. 如果一个程序可以接收外部事件(如GUI 事件): 处理模块之间加一个事件管理模块,从而将这二者解耦 :业务或组织、代表整个系统的类、完成业务的活跃对象、虚构类
2. 控制者(可以是外观控制者,或者一个纯虚构对象): 接受所有的外界请求并转发,很可能形成集中式风格
3. Controller肯定会跟两边对象都有很严重的耦合,并且由于它负责转发很 多信息,所以它的内聚度不高
6) 多态模式:当对象的行为由其类型决定时,需要使用多态的方法调用,而不是通过if/else语句来做选择。
7) 纯虚构模式:为了实现高内聚低耦合,将一组内聚性很高的职责分配给一个完全抽象的类,这个类在问题域中没有任何意义 经典场景: 分离模型的表示 分离模型的实现平台 分离复杂行为 分离复杂数据结构
8) 间接模式:为了防止直接耦合,将职责分配给一个中介对象用于部件与服务之间的交互 常见Indirection 方法:
Pipe/Event/Share Data Styles; Adapter Pattern; Proxy Pattern; Broker Pattern; Delegator Pattern; Mediator Pattern;
Publish-Subscribe or Observer pattern
9) 保护差异模式:对于将来可能发生的变化,由稳定的接口来承担对象的访问职责
二、软件设计的审美标准 1、设计的审美标准包括:
1) 简洁性(模块化):结构清晰 2) 结构一致性(概念完整性):
3) 坚固性(高质量:易开发、易修改、易复用、易调试、易维护、易理解)
2、软件设计方法与技术(至少5点)
1) 模块化:进行模块划分,隐藏一些程序片段(数据结构和算法)的实现细节,暴露接口与外界;且保证模块内部的内聚度较高,模块与外界的耦合较低。
模块隐藏实现细节,通过接口访问模块,因此促进了简洁性;且因为功能内聚,对外提供统一的外部接口,因此促进了结构一致性 2) 信息隐藏:将系统分成模块,每个模块封装一个重要决策,且只有该模块知道实现细节。决策类型可以是需求、变更,不同的决策之间相互独立。 信息隐藏和模块化都在一定程度上促进了简洁性,隐藏为了处理一些不需要对外表现的决策段分割,这又在一定程度上牺牲了简洁性而达到了坚固性
3) 运行时注册:针对系统变化,将可能变化的部分与其他部分解耦,不直接发生程序调用,而是在运行时注册。
因为这个技术针对可能的变更而使用,本来可 以用一个部件处理的事情,却需要多个部件一同完成,产生了复杂的交互规则,所以牺牲了简洁性,以提高坚固性(灵活性)
4) 配置式编程:针对系统变化,主要解决共性与差异性问题。将可能变化的部分写在一个配置文件中,当要发生变化时,直接修改配置文件。 因为需要充分考虑可能的变更来组织配置文件,并且需要在系统启动时对配置文件进行解析,所以牺牲了简洁性,以提高坚固性(灵活性)
5) 设计模式:设计模式牺牲简洁性达到坚固性,保证程序的可维护性和可扩展性。同时在设计模式中是讲究用同样的方法做同样的事,促进了程序结构的一致性
6) 体系结构风格:体系结构风格封装了一个设计机制,说明了体系结构中的重要设计决策,并且说明了与此相对应的设计约束。 体系结构风格促进了系统的一致性;由于体系结构风格有助于做好系统的高层设计,控制系统复杂度,因此促进了系统的坚固性。
7) 职责分配GRASP :促进了坚固性,一致性,有时牺牲简洁性
8) 协作设计:促进了坚固性,但有时会牺牲简洁性易理解性,例如Mediator 和Controller 中会涉及大量复杂交互
二、 设计的层次
a) 高层设计 1) 出发点:
弥补详细设计机制的不足,将一组模块组合起来形成整个系统,进行整体结构设计。同时,体系结构也是一系列对系统设计所做的设计决策 2) 主要关注因素:
项目环境,包括开发环境、业务环境、技术环境;业务目标。为了达成以上目 标,要求体系结构满足简洁性、一致性、坚固性 3) 主要方法与技术:
1. 方法:4+1 view、场景驱动、体系结构风格 2. 技术:模块的表示方法可以是box-line 、formal language(ADL ,架构描述语言) UML (4+1 view 模式使用UML 技术实现)
4) 最终制品:体系结构 b) 中层设计 1) 出发点:
模块与类结构设计;模块划分,做到接口抽象与实现的分离,隐藏实现细节(数据结构和算法),对外提供接口;模块之间尽可能独立,实现单个模块高内聚,模块之间低耦合 2) 主要关注因素:
简洁性(易开发、易修改、易复用); 可观察性“看上去显然是正确的”(易开发,易调试,易维护); 高内聚,低耦合; 3) 主要方法与技术:
1. 模块化(模块划分+内聚/耦合标准):低耦合(将模块之间的关系最小化)高内聚(将模块之内的各个方法之间的联系最大化) 2. 信息隐藏(模块化+可修改性):一个模块只封装一个secret (主要秘密是需求决策,次要秘密是修改决策)而且只有自己知道决策细节,决策有需求决策、修改决策
3. 面向对象:结合模块化和信息隐藏的方式,再加上封装、继承、多态等技术,进行面向对象的设计 4) 最终制品: 模块与类结构 c) 低层设计 1) 出发点:
将基本的语言单位(类型与语句)组织起来,建立高质量的数据结构和算法(数据结构合理易用,算法可靠、高效、易读)的实现细节 2) 主要关注因素:
数据结构与算法的简洁性(易读) 3) 主要方法与技术:
防御式编程,断言式编程,测试驱动开发,异常处理,配置式编程,表驱动编程,基于状态机编程
4) 最终制品:
算法与数据结构,单个的函数
三、 体系结构风格 1、 描述和比较各种风格
1)体系结构风格分为4个level Object Level:Design Patterns Module Level: Process Level:
Physical Unit Level:
1、模块级别
1、 主程序、子路径风格:部件从其父部件当中得到控制信息,绝不向其同级或上级发出调
用信息。
部件:过程、功能和模块。 连接件:程序调用。
约束:控制流总是由顶端延层次结构开始向下传递。
优点:处理过程清晰,易理解;能够保证正确性
缺点:不利于更改和复用;处理不当会形成公共耦合 适用:顺序处理系统;正确性要求较高的系统 2、 面向对象式风格:对象能够帮助进行封装内部的secrets ,只有通过方法才能够访问对象。
部件:对象或模块 连接件:方法调用
约束:数据的表示对其他对象来说是透明的;每个对象的数据完整性自行维护,每个对象是独立的。
优点:只要不修改接口,模块之间的可修改性很好;系统被分解为许多独立的部分 缺点:对象之间交互必须了解对方的接口;对象可能产生副作用 适用:能够把系统分解为算法+数据的结构,以进行封装 3、 分层风格:描述层与层之间的调用结构。
部件:过程或对象的集合 连接件:过程调用或方法调用
约束:系统要被严格组织成层次结构,每一层为其上层提供服务,并调用下一层,不允许跨层调用。
优点:基于增加抽象层次设计;易于修改;易用重用
缺点:很多系统无法简单的划分层次;必须按层次调用,增加高层和低层实现之间的耦合
适用:系统可以按照功能划分为不同的层次
4、 隐式调用风格:数据封装,所有调用通过事件完成
部件:agent
连接件:事件处理
约束:抛出事件的部件不了解被影响的部件有哪些;对事件的接收顺序也不能有假设;不能假设事件抛出一定有部件进行处理
优点:可重用性好,可修改性好
缺点:正确性得不到保障;调试测试会很困难 适用:通常适用于能把系统分解为松散耦合的系统
5、 管道、过滤器风格:每个Filter 都能处理数据然后传递给下一个Filter ,Filter 可以在任意
处处理数据,各个Filter 之间无数据共享,通过Pipe 交互。 部件:Filter 连接键:Pipe
约束:Filter 之间不共享数据;Filter 并不了解上下Filter ;单个Filter 的正确性并不依赖上下的Filter
优点:易于理解,支持复用,易于维护和扩展,对于特殊的要求可以满足,支持并发。
缺点:不能很好的处理交互问题,传输数据需要额外的空间,可能丢失额外的性能并增
加复杂度
适用:系统可分解为多个可并行的任务
6、 存储库风格:各个客户端读写同一块存储区域
部件:一个中心数据结构表示系统状态,一系列独立部件含有在中心数据结构上的操作。 连接件:过程调用或直接内存访问
约束:所有的agent 都是独立的;每个agent 都依赖共享数据;agent 对数据进行操作。
优点:可以充分存储大量数据,空间性能优越;减少复杂数据的复制
缺点:必须能够建立稳定的中心数据区;Blackboard 可能会成为瓶颈;数据的演化代价很高
适用:可以建立一个中心数据区,并且维护复杂的中心信息。 7、 MVC 风格:Model 与Controller 、View 分离。
部件:Model 部件用来维护领域信息,通知View 的更改;View 部件用来给用户展示信息,发送用户请求到Controller ;Controller 部件用来更改Model 状态,对用户请求响应。 连接件:系统调用、消息、时间、直接内存访问
优点:允许多态,在一个模型中有独立的View ;Views 能够被序列化;可以任意增加删除View 和Controller
缺点:增加了复杂度;在View 中不能有效获得数据访问;与现代用户界面工具不是特别兼容
适用:接口的更改很容易并且可能发生在运行时;用户界面的修改并不会影响逻辑代码
2、 进程级别
1、 点对点风格:分布式系统中异步消息,可能多个应用程序需要接受同一个消息;发送者
和接受者之间的松散耦合的;传输是基于事件接受的;事件存在层级结构
部件:发送者、接受者 连接件:事件路由
3、 物理级别
1、 C/S风格
部件:Client 、Server
连接键:基于RPC (远程过程协议)交互协议
2、 3层结构风格
包括表示层、业务逻辑层和数据层
3、 端对端风格
部件:部件即作为Client 也作为
Server
连接件:同步异步消息;不共享内存
4、 分布式风格
网络上所有的物理节点都是平等的,使用时透明。
2、对给定场景,判断需要使用的风格
1) Main Program and Subroutine主程序和子程序风格:对正确性要求很高的系统
2) Object-Oriented面向对象风格:数据展示和相关操作被封装在抽象数据类型中
3) Layered分层风格:能够把程序分成不同的层次,层次之间的协议是稳定的,程序可能只在某一层内部发生修改。比如OSI 七层模型,比如UNIX 系统
4) Pipe-Filter管道过滤器风格:任务需要独立执行,那么便适用于此风格。比如有顺序的批处理程序、UNIX/Linux命令管道、编译器、信号处理、大数据量处理
5) Implicit Invocation隐式调用风格:通常适用于松散耦合系统,部件之间关联比较少,每一个部件都有它独立的操作,但是会有事件发生需要用到某些操作。 比如调试系统、数据库管理系统、GUI
6) Share Data共享数据风格:大型信息系统,能够将数据做集中处理,并且需要 维护一个复杂的中心信息。常见如数据库、专家系统、编程环境。在网络应用中,网络日志使用repository 风格,而网络聊天室使用blackboard 风格
五、详细设计
1、职责分配的协作设计
1、协作设计(控制风格)的比较和场景判定
1) 集中式:将整个系统逻辑集中在某一个controller 模块中。
controller 并不做具体的事情,很容易看出决策,工作流程很容易理解,不过会形成臃肿的Controller ,并且Controller 难以理解、维护和测试。Controller 会把其他模块看作是存储库,这样形成了不必要的耦合,也违反了信息隐藏。不推荐使用
优点:系统容易找到决策点与决策依据
缺点:controller 可能变得过于复杂,难理解、难测试、难维护;
controller 将其他模块作为知识库,这增加了系统模块间的耦合度,破坏了信息隐藏原则
2) 分散式:将整个系统逻辑分散在系统模块中,没有一个集中的controller 。
虽然分散式控制风格符合面向对象的思想,但是很难看出整体的工作流,因此耦合度高,
内
聚性低,并且很难隐藏信息。不推荐使用
优点:每一个对象都有很少的行为、维护很少的信息
缺点:由于每个对象都可以有控制逻辑,导致整个系统的控制流很复杂,难被理解
每个对象都可能需要依赖其他对象才能完成某件事情,导致模块间耦合度高
很难做到信息隐藏
对象的内聚性也很差
其他的一些模块化原则也很难满足
3) 委托式:决策制定工作分散在对象网络中,但是会有几个controller 做主要决策,每一个controller 只绑定少数几个component ,支持信息隐藏以及部分的分散。委托式位于完全集中和完全分散之间。推荐使用
优点:每个controller 一般仅绑定少数几个component ,降低模块间耦合度
更好地支持信息隐藏
系统容易划分层次与模块
2、对给定场景和要求的控制风格,根据GRASP 模式,判断特定职责的分配
低耦合:能不发生系统调用就不发生,委托给别人去调用,比如A 、B 、C ,A 调用B ,让B 调用C 去完成其他事情,而不是A 再调用C ,与C 发生耦合
高内聚:单一职责才能高内聚,将不相关的行为分给别的类
专家模式:谁有信息那个职责就分配给谁
创建者模式:对象创建职责分配的问题。A 聚合了B 的对象;A 包含了B 的对象;A 记录了B 对象的引用;A 使用了B 的对象;在A 中包含初始化B 对象的数据
控制者模式:当有事情发生时,应该由一个controller 来做转发这件事,譬如post 做enterItem 这件事,而不能由GUI 与Sale 直接耦合,这样Sale 也不符合单一职责原则
多态:用多态代替if „else 语句。将决定行为的“类型”继承一个公共的接口,譬如CashPayment 、CreditPayment 、CheckPayment
纯虚构模式:虚构出一个问题域中不应该存在的对象来做某些事情,例如数据库模块做save sale 这件事,使得sale 本身保持高内聚性;常用的纯虚构:表现与模型分离、平台与模型分离、复杂行为分离、复杂数据结构分离
间接模式:加中间对象将原本耦合的对象进行解耦。常用的有:Pipe/Event/Share Data Styles、Adapter Pattern 、Proxy Pattern 、Broker Pattern 、Delegator Pattern 、Mediator Pattern 、Publish-Subscribe or Observer pattern
保护差异模式:将可能变化的部分单独封装并维护一个稳定的接口,使得变更时不会对外部造成影响,常用的有:Information Hiding 、Data driven (configuration files) 、Service lookup (runtime registration)、Interpreter-Driven(generalize module)、Reflective or Meta-Level Designs (Component replace)、Uniform Access (adherence to protocols)、LSP (polymorphism)、Law of Demeter (restrict communication paths)
3、根据分析类图和体系结构模块接口,建立基本的设计类图
详细设计过程:
(1)面向对象/控制风格(委托式)
(2)找出概念类以及类重要属性
(3)找出行为
(4)进行职责分配
(5)画详细类图
六、设计模式
1、课后思考题
注:设计模式重点
集合类型的封装不完整 (Iterator )
层次结构的缺失 (Facade )
程序调用的强绑定(运行时注册)问题 (Observer )
2、如何针对集合类型,做到Programming to Interfaces
Programming to Interfaces即一个模块在于外部交互时应该严格依赖接口,而不暴露内部实现 三种方式:Iterator Pattern、Proxy Pattern、Prototype Pattern
(1)Iterator Pattern:提供了一种方法顺序访问一个聚集对象中的所有元素,同时又不必暴露该对象的内部表示;同时可以支持对聚合对象的多种便利方式;同时为便利不同的聚合结构提供一个统一的接口(即支持多态迭代)。Iterator Pattern类图如下:
(2)Proxy Pattern:为其他对象提供一种代理以控制对这个对象的访问,
例如对集合对象进
行访问时可控制访问的具体方式,而这种访问方式同时保持对客户的透明性,例如IteratorX ,IteratorY ,IteratorZ 是集合对象不同维度上的访问。Proxy Pattern类图如下:
(3)Prototype Pattern:
3、OCP 的手段[提示:掌握继承机制,但要了解其他机制] 所谓OCP 即指对扩展开放(当新需求出现的时候,可以通过扩展现有模型达到目的),对修改关闭(对已有的二进制代码,不允许进行修改)。
实现OCP 原则的关键是抽象与封装。利用抽象封装完成对需求可能发生变更的部分进行处理,具体处理手段如下:
(1)使用多态的方式,做一个继承树。此方案针对会发生修改但不是很严重的地方,让需求扩展的实体继承已经存在的实体。
(2)使用继承与组合联合的方式。
例如:
1) Decorator Pattern
2)
Strategy Pattern
3) State Pattern
4) Bridge Pattern
(3)延迟绑定:
运行时注册:使用Event style或Observer pattern实现运行时注册的方式,当需要进行扩展时,就让扩展的方法监听某个事件,事件发生时这个方式就会被调用(service lookup) 配置文件:使用配置文件进行启动时绑定,将需要修改、扩展的信息写在配置文件中,通过解析配置文件来决定做什么事情(Data Driven)
继承多态方式(LSP )
构件更替:如果需要修改、扩展,则在加载模块的时候使用修改/扩展的模块,实现加载绑定。一般是将变化的部分写在一个.dll 文件中,变化的时候直接更新.dll 文件。(Reflective or Meta-Level Design)
预定义协议:在两个之间预定义协议,然后各个进程可以独立进行变化,只要通信协议不变。例如TCP/IP等协议(Uniform Access)
(4)信息隐藏:
(5)泛化模块:Interpreter-Driven
(6)限制交互路径(迪米特法则:一个对象应该对其他对象有尽可能少的了解)
4、一个模块的信息隐藏有哪两种基本类型,各自有哪些典型的处理手段
1、需求:即一个模块的接口功能与模块内部程序细节的分离
手段:
给出接口,隐藏接口的实现
封装设计决策,使得只有本模块知道设计决策的实现方式
若有一系列重要决策,则将系列决策均进行封装
如果有系列的可能的设计变更,则将这些可能的设计变更进行封装
各设计决策之间相互独立
使用Facade 模式
使用Controller
2、变化:将要发生变化的程序部分封装起来
手段:
给出将要修改部分的接口,隐藏接口的实现部分
将需求变化封装在各自的类,子程序或者设计单元内
Strategy Pattern(仅仅是部分行为或算法实现有变化)、State Pattern(对象的行为因属性的不同而变化)、Bridge Pattern(接口与实现都有差异)
5、实现共性与可变性有哪些手段?对给定的场景,给出共性与可变性的设计方案。
多态(继承)与聚合
其中多态(继承)适合于1 of N的情况,父类中封装共性部分,子类中封装可变性部分 聚合适合于M of N的情况,whole 角色类封装共性部分,part 角色类封装可变性部分
(1)多态(继承) :1 of N
(2)聚合:M of N
1、如果一个对象集之间除共性外,有超过2个的差异行为,如何处理?
Strategy Pattern:两个Strategy 类中各有个AlgorithmInterface
2、如果一个对象集的部分行为组存在差异性,如何处理?
Strategy Pattern:每个Strategy 类中有两个AlgorithmInterface
3、如果一个对象集的部分属性(以及依赖于这些属性的方法)存在差异性,如何处理? 将属性以及方法均抽取出来置于Strategy 类中
4、如果一个对象集的一个行为需要协作对象来完成,但是它们的协作对象存在差异性,如何处理?
将“调用协作对象”这一过程置于Strategy 中,Command Pattern的变体
5、如果一个对象集的行为因为属性的取值而存在差异性,如何处理?
State Pattern
6、运行时注册的主要机制、适用场景与优缺点
1、 Observer Pattern
意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新
适用场景:
(1)当对一个对象的改变需要同时改变其它对象,而不知道具体有多少对象有待改变。
(2)当一个对象必须通知其他对象,而他又不能假定其他对象是谁。换言之,你不希望这些对象四紧密耦合的。
(3)当一个模型有两个方面,其中一个方面依赖于另一个方面。将这二者封装在独立的对
象中以使它们可以各自独立的改变和复用。(PPT 上无)
优点:
灵活性、可变性、复用性得到保证。目标与观察者之间耦合程度降低,实现抽象耦合,允许独立的改变目标和观察者,可以复用目标对象而无需同时复用其观察者。
支持广播通信,目标者不必知道观察者是谁(PPT 无)
缺点:
增加系统复杂程度,对于系统的理解和测试更加困难。
一个观察者的无意的更新可能引起其他观察者的意外的更新,也容易引起更更新错误,而这种错误难以捕捉。(PPT 无)
2、Event Style
相比Observer Pattern,可以实现对多个事件的监听。即实现对象间多对多的依赖关系。 其他特点同Observer Pattern
3、Command Pattern
意图:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;请求排队或记录请求日志,以及支持可撤销的操作。
适用场景:
(1)抽象出待执行的操作以参数化某对象。
(2)在不同的时候制定、排列和执行请求。
(3)通过在实施操作之前将状态存储起来以支持撤销操作。
(4)支持修改日志,这样当系统崩溃时,这些修改可以被重做一遍。
优点:
将调用操作的对象与知道如何实现该操作的对象解耦。
可以将命令存储在栈或队列中以支持请求排队,命令处理模式维护一个历史信息。
可以容易的支持undo 和redo 操作,但是必须存储额外的状态信息以避免滞后影响问题。 扩展Command 对象很容易。
7、对象创建有哪些方法
1、简单场景:
Creator 创建者:对于A 、B 两个对象,在以下情况下A 创建B 对象:A 聚合了B 的对象;A 包含了B 的对象;A 记录了B 对象的引用;A 使用了B 的对象;在A 中包含初始化B 对象的数据
Coupling 低耦合:在A 聚合、包含、记录、使用B 的情况下,如果将创建B 对象的职责赋予其他对象,则A 需要与其他对象产生多余的耦合
Cohesion 高内聚:在A 中包含初始化B 对象的数据的情况下,则A 为信息专家,根据高内聚原则,A 应当承担B 对象创建的职责
2、复杂场景:
(1)场景一:仅仅一个实例允许被创建
使用Singleton Pattern ,首先将Constructor 私有化;声明一个static private 的类实例;创建一个public static的getInstance 方法使得外部类可以通过此方法获得此类实例。并且此方法如果用于多线程,要注意声明为protected/synchronize以保证线程安全
(2)场景二:实例个数有限制
以singleton 为基础进行改进
(3)场景三:一个类不知道它所必须创建的对象的类;一个类希望有他的子类来制定它所创建的对象;类将创建对象这一职责委托给多个帮助子类中的一个,并且希望将哪一个帮助子类作为代理者这一信息局部化
Factory Method
(4)场景四:控制子类拓展,子类与父类的算法框架相同,但局部实现方法不同 Template Method Pattern
(5)场景五:多个需创建的类实例之间存在类型依赖关系
Abstract Factory Method
(6)场景六:实例的创建和初始化很复杂,例如运行时刻制定要实例化的类;初始化时变量值发生变更
Prototype Pattern
七、重构
1、对指定案例,判断Bad Smell并进行重构
1、代码存在冗余:Duplicated Code
解决方案:将相同的方法移至父类;
(1)同类中有代码重复:将方法提取出来
(2)2个相关类的方法:将相同的方法提到父类中实现
(3)2个完全不相关的类:构造第三个类,将相同的部分委托于第三个类(或者二者中找出一个来充当第三个类)
2、一个类的职责过多:Long Method
解决方案:将其分成若干个具有单一职责的类
3、一个类(非Controller )包含了太多不相关的东西(对外接口过多):Large Class 解决方案:将不必要的接口隐藏起来
4、一个类有多个改变的原因,影响了内聚:Divergent Change
解决方案将这个类重新划分成若干个小类
5、两个方法在逻辑上极其相似:Short Gun
解决方案:在其他类中将这个方法集聚起来(可以新建类也可以在已有类中)
6、一个类中只有数据(违背了内聚、信息专家原则):Data Class
解决方案:封装成Field 或者封装成Collection
除去Setter
将其他类中有关于这个类中属性的方法移到这个类中
如果不能移动整个方法,可以提取方法中的一些操作
7、一个方法中参数太多
解决方案:
(1)同时做了太多的任务:
将方法划分成几个子方法
(2)有许多分散的子部分
将参数组合成一个类或者其他的数据结构传入
8、一个类对另一个类表现出了过多的关注:Feature Envy
解决方案:将一些方法移至数据所在的类中
9、多个参数被多个方法同时处理:Data Clumps
解决方案:信息隐藏不佳,需要将这几个参数进行封装
10、基本型别偏执:Primitive Obssession
解决方案:如果满足既有数据又有方法,那么就可以将这些基本类型封装成类
11、出现Switch 语句:Switch Statements
解决方案:用多态来代替条件判断
12、平行的继承层次:Parallel Inheritance Hierarchies
解决方案:差异性处理问题,两种差异性组合
把其中一个实例指向另一个,用聚合结构代替继承
13、懒惰的类:Lazy Class
解决方案:这种情况经常在维护中出现
把这个类降解到其父类或者拆分成参数
14、过度设计:Speculative generality
解决方案:避免过度设计,需要拓展的可以通过重构实现
15、临时栖息地:Temporary Field
解决方案:体现在难以理解的代码,对于判断语句后的非对称方法调用
将这些使用没有归宿的参数包括在新提取出来的类中
16、消息链:Message chains
解决方案:隐藏代理
(1)如果调用的是类库,那么一般没有问题
(2)如果调用自己的方法,那么就先调用一次将中间值附给中间变量
17、中间人:Middle man
解决方案:去除中间人,使用集成或者聚合来代替
18、不适当的亲密Inappropriate intimacy
解决方案:两个类之间共享了太多的信息,耦合很大
合理使用getter 和setter
重新考虑抽象
如果发现他们之间是“真爱”,那么就将其合二为一
19、实现相同功能的类具有不同的接口:Alternative classes with different interfaces 解决方案:将这两个类合二为一,统一接口
20、不完整类库:Incomplete Library Class
(1)如果只有少数方法没有实现,引进外来的方法
(2)如果有很多方法未实现,在原有基础上继承
总而言之,不能直接修改类库
21、被拒绝的馈赠(子类与父类中定义的方法想矛盾,子类继承了父类的一些方法,但并不打算使用它):Refused bequest
解决方案:
(1)将这个方法延后实现
(2)用代理来代替继承
22、注释不当:Comments
解决方案:无固定方案,看着办吧