第12章 详细设计概述
中层设计+低层设计:实现所有功能性+非功能性需求
1.详细设计的出发点
需求开发的结果(需求规格说明和需求分析模型)和软件体系结构的结果(软件体系结构设计方案与原型)
明确职责建立静态模型(设计类图),明确协作建立动态模型(详细顺序图)
GRASP(General Responsibility Assignment Software Patterns)(1)信息专家模式
基本的职责分配原则之一,把职责分配给具有完成该职责所需信息的那个类
例如:总价(委托)——数目——单价——促销策略:耦合没有增加
总价(得到)——数目、单价、促销策略:增加了耦合(总价知道太多)
优点:促进低耦合、高内聚、维护封装(2)控制器
处理外部事件(用户和系统时钟发生的外部交互)
核心思想:解耦
不要界面直接调用代码,也不要代码直接调用界面,把系统/人/用例作为controller(3)创建者模式
根据潜在创建者类和被示例话类之间的关系决定哪个类创建实例
当满足以下的条件时,B创建A:B“包含”A,或者B组合A;B直接使用A;B拥有A的初始化数据;B记录A(4)纯虚构
软件特有的ui、DAO、RMI、文件读写、复杂行为、设计模式、复杂数据结构、界面与逻辑层的model,不属于现实世界
作用:保持代码可复用性,高内聚、低耦合
2.职责分配
通过职责建立静态模型:面向对象分解中,系统是由很多对象组成的。对象各自完成相应的职责,从而协作完成一个大的职责。类的职责主要有两部分构成:属性职责和方法职责。类与类之间也不是孤立存在的,它们之间存在一定的关系。关系表达了相应职责的划分和组合。它们的强弱顺序为:依赖<关联<聚合<组合<继承。
3.协作
根据协作建立动态模型:(1)从小到大,将对象的小职责聚合形成大职责;
(2)大到小,将大职责分配给各个小对象。通过这两种方法,共同完成对协作的抽象。
4.控制风格
为了完成某一个大的职责,需要对职责的分配做很多决策。控制风格决定了决策由谁来做和怎么做决策
分散式:所有系统行为在对象网络中广泛传播
集中式:少数控制器记录所有系统行为的逻辑
委托式(授权式):决策分布在对象网络中,一些控制器作主要决策
5.建立设计类图或详细顺序图
抽象类的职责->抽象类之间的关系->添加辅助类
辅助类:接口类、记录类(数据类)、 启动类、控制器类、实现数据类型的类、容器类
6.协作的测试
MockObject
第13章 详细设计中的模块化与信息隐藏
1.耦合与内聚(名词解释)
(1)耦合
描述的是两个模块之间的关系的复杂程度。
耦合根据其耦合性由高到低分为几个级别:模块耦合性越高,模块的划分越差,越不利于软件的变更和重用。1、2、3不可接受,4、5可以被接受,6最理想
(1)内容耦合(一个模块直接修改另一个模块的内容,如GOTO语句;某些语言机制支持直接更改另一个模块的代码;改变另一个模块的内部数据)
(2)公共耦合(全局变量,模块间共享全局数据,例子:全局变量)
(3)重复耦合(模块间有重复代码)
(4)控制耦合(一个模块给另一个模块传递控制信息,如“显式星期天”)
(5)印记耦合(共享数据结构却只是用了一个部分)
(6)数据耦合(模块间传参只传需要的数据,最理想)
(2)内聚
表达的是一个模块内部的联系的紧密性。
内聚性由高到低分为:内聚性越高越好,越低越不易实现变更和重用,3、4、5等价,1、2最好,6、7不能接受。
(1)信息内聚(模块进行许多操作,各自有各自的入口点,每个操作代码相对独立,而且所有操作都在相同的数据结构上进行:如栈)
(2)功能内聚(只执行一个操作或达到一个目的)
(3)通信内聚(对相同数据执行不同的操作:查书名、查书作者、查书出版商)
(4)过程内聚(与步骤有关:守门员传球给后卫、后卫传给中场球员、中场球员传给前锋)
(5)时间内聚(与时间有关:起床、刷牙、洗脸、吃早餐)
(6)逻辑内聚(一系列可替换的操作:如坐车、坐飞机)
(7)偶然内聚(多个不相关的操作:修车、烤面包、遛狗、看电影)
【题型】对实例,说明它们之间的耦合程度与内聚,给出理由。书上P226习题
2.信息隐藏基本思想
每个模块都隐藏一个重要的设计决策——职责。职责体现为模块对外的一份契约,并且在这份契约之下隐藏的只有这个模块知道的决策或者说秘密,决策实现的细节仅自己知道。
模块的信息隐藏:模块的秘密(容易变更的地方):根据需求分配的职责、内部实现机制。
【题型】对实例,说明其信息隐藏程度好坏。教材222页
【项目实践】耦合的处理?
分层风格:仅程序调用与简单数据传递
包设计:消除重复
分包:接口最小化
创建者模式:不增加新的耦合
控制者模式:解除View与Logical的直接耦合内聚的处理?
分层:层间职责分配高内聚,层内分包高内聚
信息专家模式
控制器与委托式控制风格信息隐藏处理?
分层与分包:消除职责重复、最小化接口:View独立?数据库连接独立?
模块信息隐藏:模块需求分配与接口定义
类信息隐藏:协作设计,接口定义
变化设计?分层风格、RMI
第14章 详细设计中面向对象方法下的模块化
模块化的原则,教材229页
1)全局变量是有害的
2)显式(代码清晰,不进行不必要的隐藏,但可能与可修改性冲突)
3)不要有代码重复
4)针对接口编程
5)迪米特法则(不要跟陌生人说话)
6)接口分离原则(ISP)
7)Liskov替换原则(LSP:子类型可替换掉基类型,一棵满足lSP的继承树耦合度是1)
8)使用组合代替继承(适用于不符合LSP但想要进行代码复用)
9)单一职责原则
其中1、2、3针对降低隐式耦合,4、5、6针对降低访问耦合,7、8针对降低继承耦合,9和信息专家原则用于提高继承内聚,亦可以看作是降低继承耦合之用。
注1:级联调用的问题在:难以应对变更+代码难理解。应对的方法:采用委托式调用。
注2:何时接口分离:出现于有一个负责各种交互的公共接口与各个具体类直接存在使用关系,使得权限混乱,可能造成不必要的麻烦。解决方案:保留大接口并提取出几个小借口,通过具体类使用小接口,再用大接口实现小接口即可。
【题型】对给定的示例,发现其所违反的原则,并进行修正。
练习题/教材243页习题/教材258页习题
第15章 详细设计中面向对象方法下的信息隐藏
1.信息隐藏的含义
需要隐藏的两种常见设计决策
需求(模块说明的主要秘密)与实现——暴露外部表现,封装内部结构
实现机制变更(模块说明的次要秘密)——暴露稳定抽象接口,封装具体实现细节
面向对象机制
封装:封装类的职责,隐藏职责的实现+预计将要发生的变更
抽象类(接口)/继承(实现):抽象它的接口,并隐藏其内部实现
2.封装
(1)含义1:数据与行为集中在一起
含义2:接口与实现相分离:一个类可以分成两个部分,接口和实现
接口是类的外部表现,对外公开;实现是类的内部结构,内部隐藏
(2)封装的初始观点:把数据(内部结构)隐藏在抽象数据类型内
新观点(信息隐藏):隐藏任何东西:数据与行为、复杂内部结构、其他对象、子类型信息、潜在变更
(3)封装数据与行为:除非(直接或间接)为满足需求(类型需要),不要将操作设置为public。类型需要的操作:为了满足用户任务而需要对象在对外协作中公开的方法,例如下图的4个操作(属于后一个对象,为满足计算商品总价的任务)
除非(直接或间接)为满足需求(类型需要),不要为属性定义getX方法和setX方法,更不要将其定义为public。例如上一示例中的getPrice()
(4)封装结构:不要暴露内部的复杂数据结构,经验表明复杂数据结构是易于发生修改的。例如暴露了内部使用List数据结构。
改进:Iterator模式(所有涉及到集合类型的操作都可能会出现此问题)
(5)封装其他对象:委托而不是提供自己拥有的其他对象的引用
除非Client对象已经拥有了该其他对象的引用,这时返回其引用不会增加总体设计的复杂度
可以保证Sales只需要关联SalesList,不需要关联SalesLineItem和Commodity;从整个设计来讲,Sales不需要知道SalesList里面存储的是SalesLineItem的集合,更不需要知道SalesLineItem使用了Commodity类型
(6)封装子类(LSP:子类必须能够替换他们的基类)
(7)封装潜在变更:识别应用中可能发生变化的部分,将其与不变的内容分离开来
封装独立出来的潜在变化部分,这样就可以在不影响不变部分的情况下进行修改或扩展( DIP 和OCP)
3.开闭原则OCP
对扩展开放,对修改封闭
违反了OCP原则的典型标志:出现了switch或者if-else
分支让程序增加复杂度,修改时容易产生新错误(特例:创建)
4.依赖倒置原则DIP
(与工厂结合紧密,解决new的创建问题)
I. 高层模块不应依赖底层模块,两者都应依赖抽象
II. 抽象不应依赖细节,细节应依赖抽象
使用抽象类(继承)机制倒置依赖
示例:A依赖于B:B不是抽象类,所以A依赖于具体,而不是抽象,如果需要变更B的行为,就会影响到A
添加抽象类BI,让 B实现(继承)BI:A依赖于BI,B依赖于BI,BI是抽象类,所以是依赖于抽象,BI比较稳定,如果B发生变更,可以通过为BI扩展新的实现(子类型)来满足
链接
👉软件工程与计算II重点整理(第1-5章)
👉软件工程与计算II重点整理(第6-7章)
👉软件工程与计算II重点整理(第8-11章)
👉软件工程与计算II重点整理(第16-19章)
👉软件工程与计算II重点整理(第20-23章)