文章目录
详细设计基础
什么是详细设计
- 体系结构设计表达系统高层设计抽象——表达模块之间的交互、接口等
详细设计落实到具体的模块之中去,(面向对象的详细设计下)模块中的对象、类是如何设计的

- 详细设计是模块内部具体设计机制
中层上描述:类的规格(类接口、类之间如何交互等)
低层上描述:数据结构、算法等(类的实现、方法实现) - 详细设计也是一种设计,也需要设计师考虑美学、功能和其他的一些重要属性(如可修改性、可维护性等)
详细设计的出发点
详细设计的输入为:需求规格说明与体系结构设计(到这个阶段我们手里有的)
详细设计的上下文
上下文即指详细设计的输入、输出都是什么
建桥的案例
- 详细设计的输入

- 从需求、体系结构设计到详细设计
体系结构设计规定了模块之间结构、接口等
详细设计的工作是进入模块内部的

详细设计的上下文的思考
- 开始详细设计之前的思考
需求开发阶段中的工作,哪些会影响到详细设计?
软件体系结构设计阶段中的工作,哪些会影响到详细设计? - 详细设计的出发点
来自体系结构设计工作,模块的规格(模块对外交互的供、需接口等)
来自需求开发工作,功能职责,即要实现的功能(还有一些功能从实现决策中来);功能常用用例、领域模型(即概念类图)、流程图、状态图等表达 - 需求开发工作中会被此阶段用到的图


- 软件体系结构设计工作中可能被用到的
构建之间的接口//被Presentation层调用接口,逻辑层被展示层调用的,供接口 public interface SalesBLService{ public CommodityVO getCommodityByID(int id); public MemberVO getMember(); public CommodityPromotionVO getComodityPromotionByID(int commodityID); public boolean addMember(int id); public boolean addCommodity(int id, int quantity); public int getTotal(int mode); public int getChange(int payment); public void endSales(); } //调用DataService层的接口,展示层调用数据层的接口,需接口 public interface SalesDataService extends Remote{ public void init() throws RemoteException; public void finish() throws RemoteException; public void insert(SalesPO po) throws RemoteException; ... } - 详细设计的输出
输出的是模块内部的实现(包内部、模块内部)(还会有一些繁殖循环依赖的设计)

面向对象的详细设计
面向对象设计的思想
职责
- 职责就是义务
职责包括做任务、维护数据等工作
做任务——行为职责——对应类对象的成员方法
维护数据——数据职责——对应类对象的成员变量
数据和行为被封装,一起体现职责 - 职责驱动、职责分配
职责的说明可以是不同抽象层次上的
职责可以被分解:大职责可以分解成小职责,由小职责合作形成大职责
高层次职责(大职责)对应大的抽象部件;小职责对应小的抽象部件
职责的分解可以指示部件的分解 - 职责驱动和职责分配的工作:将系统职责一点点地从上到下分配
操作方式:自顶向下、自底向上、两种结合都可以,都算职责驱动 - 职责分配和职责驱动的注意事项
一个更高的原则:职责分配时,要做到高内聚,低耦合(每个职责本身高内聚,职责之间交互要少,即低耦合)
模块职责不可以重叠
职责的数据与行为放在一起,共同体现一个职责 - 代理(Delegation)
面向对象的职责处理中,还会有代理机制
代理机制应该更多地被使用,要做一件事时,如果有模块声称自己有此职责,直接交给该模块处理就行(自己就不用管了)
协作
- 什么是协作
协作完成更大职责;一个程序中的对象必须协作,这样他们才能完成更大职责,如果没有协作的话就只能写出一个做所有工作的对象
将系统与社会类比,对象相当于社会个体,协作可以视为个体之间的协作(模拟社会分工)
通过协作,应用被分解为不同行为,不同行为由不同的对象完成,每次协作都可以实现或多或少的系统功能
面向对象的应用是一个关系网络,每个个体职责清晰,只要搞好自己的事情就好;社会的复杂性落在了网络之间的关联上
结构化编程应用:清晰的关系结构和交互,复杂的方法
机制层面上,协作是通过发消息完成的
语言层面上,协作是通过方法调用完成的 - 协作的重要性
通过协作完成目标功能
如果协作设计的不好,系统容易出现问题
面向对象详细设计的过程
概述
- 过程图示

- 静态设计模型就是运行时候的snapshot,主要是画一些类图
抽象类的职责——根据已有的需求的类图,需求的类图转向设计类图
抽象类之间的关系——之前是实体之间的联系
添加辅助类——在概念类图基础上(概念类图的重点全是业务类、业务特征)考虑业务之外的辅助(如消除循环依赖的接口类,这个类就是单纯的设计行为产生的类,而不是业务的类) - 通过协作建立动态设计模型,主要是画顺序图
明确对象的创建——谁来创建、时机为何
选择合适的控制风格——给交互定义风格(如集中式,分散式,代理式等) - 设计模型重构:根据准则判断当前设计的好坏,并作出优化
重构思想:模块化和信息隐藏(两个跨与编程范式的更高层抽象的思想)
设计模型建立
通过职责建立静态设计模型
抽象对象的职责
- 类表达了对对象族本质特征的抽象
一个类对应多个对象,他们的共有特征(属性、方法等) - 构建的蓝图
- 职责:数据职责、行为职责
- 单一类图示例

-代表私有,+代表公开
类图三部分:类名,属性,方法
方法最后的是返回值类型
抽象类之间的关系
关系
- 类之间的关系
| 关系 | 关系短语 | 解释 | 多重性 | UML表示法 | 关系类型 |
|---|---|---|---|---|---|
| 普通关联 | A has a B | 某个对象会长期持有另一个对象的引用;关联的两个对象之间没有任何强制性的约束 | A: 0…* B: 0…* | ![]() 没有方向限制 | 对象之间关系 |
| 聚合 | A owns B | 它暗含着一种集合所属关系;被聚合的对象还可以再被别的对象关联,故被聚合对象是可以共享的(B是被聚合对象)(部分存在,整体不存在是有可能的) | A: 0…1 B: 0…* 集合可以为空 | ![]() 空心棱形一侧连接代表整体的对象 | 对象之间关系 |
| 组合 | B is part of A | 它既要求包含对象与被包含对象之间的拥有关系,有要求包含对象与被包含对象的生命周期相同;被包含对象还可以被别的对象关联,所以被包含对象是可以共享的,然而绝不存在两个包含对象对同一个被包含对象的共享(可以共享的只能是其他关联) | A: 0…1 B: 1…1 整体存在,部分一定存在 | ![]() 实心棱形一侧连接代表整体的对象 | 对象之间关系 |
| 继承 | B is an A | 继承是一种非常强的关系;子类会将父类所有的接口和实现都继承回来;但是,也可以覆盖父类的实现 | 无 | ![]() 空心三角一侧连接抽象类那一方 | 类之间关系 |
| 实现 | B implements A | 类实现接口,必须实现接口中所有的方法 | 无 | ![]() 空心三角箭头一侧指向抽象那一方,即接口 | 类之间关系 |
| 注:多重性简单来说是指两个类“一对多还是多对多还是多对一还是别的” |
- 类图示例

GRASP模式
- GRASP模式是一种更加高层抽象而基础的原则,致力于指导如何将职责分配给类
- GRASP模式:
低耦合(后述)、高内聚(后述)、信息专家、创建者、控制器 - 基本原则:要选内聚性好的,耦合性好的,稳定性好的
决策时,面对多个选择,考虑未来变化,即可修改性 - 信息专家
面向对象设计分配职责时,依据是:哪个类拥有实现职责必须的信息,就将这个职责分配给哪个类
如果有知道职责100%信息的类,那么就将职责分配给这个类
如果两个类对该职责信息知晓比例是90% vs. 10%,那么倾向于给90*信息的那个人
如果是50% vs. 50%,就看这个职责放在哪个类里合适(因为往类里添加职责是对类有影响的):看看加入某个类后,类的意义是否被破坏(如下面的销售和商品的例子中,如果商品类中有了计算小记价格的功能,商品类就被破坏了);还可要看关联的方向(谁持有谁的引用)(如下面商品的例子,是SalesItem持有商品的引用而不是商品持有SalesItem的引用;即看是互相持有还是一方持有另一方) - 信息专家的案例
哪个类应该分配到“计算一次销售的总价格”的职责(Sales还是SalesLineItem)
由图可知:Sales持有所有SalesLineItem对象,含有计算总价的所有信息;SalesLineItem可以计算小记但是不能得到总价
即Sales是该职责的信息专家,职责应该分给Sales类
通过遍历计算总价
- 信息专家的原理
保持信息的封装,有利于低耦合、高内聚 - 只能热水器的例子
智能控制水温:特定时间水温会有明确的该高还是该低规则
概念模型
怎么知道当前时间是该升温还是降温:三种方法
- 方法一;Controller自己保存特殊事件并计算(比较当前时间和特殊时间)
这样的Controller类,low、high只与do()有关,specialTime只与judge()有关,这样就是一个类内多个职责,不好- 方法二:由SpecialTime类保存特殊时间;Controller调用getSpecialTie()得到特殊时间,再计算
此时,SpecialTime类是数据提供方
这样,判断是否是特殊时间的职责所需信息在SpecialTime类里,而职责方法在Controller类里,即数据与行为分离,不好- 方法三:由SpecialTime类保存特殊时间,并提供isSpecialTime();Controller调用方法
此时,Controller调用isSpecialTime()方法,直接得到结果;这是委托SpecialTime类来做判断;SpecialTime类被委托
这个是好的
思考:为何这个例子中getSpecialTime()的方案(数据提供机制)不合适,而Sales例子中getSalesItem()可以?
解答:如果职责全给商品,商品也算不出来(即二者都不是100%信息专家,数据提供不可避免)
添加辅助类
- 辅助类:设计中出现的类而前面过程没出现的类
接口类、记录类/数据类(记录:专门用来存储信息的对象,可能是持久化对象PO)、启动类(负责系统启动)、控制器类(作为一个Controller,起协调、转发的作用,也业务无关而与系统有关,进行对系统实现的控制,所以才在添加辅助类阶段添加这个类)、实现数据类型的类、容器类等 - 辅助类与业务无关,但是需要他们的辅助使系统运作起来
- 添加辅助类后的设计模型:更加丰满

通过协作建立动态设计模型
这个阶段看的是运行起来的样子
抽象对象之间协作
- 抽象协作的方法:建立大小职责的联系;跨过鸿沟,建立起职责网络
从大到小,将大职责分配给各个小对象
从小到大,将对象的小职责聚合形成大职责(每个类都是小职责) - 用顺序图表示对象之间的协作(对象之间通过消息传递完成大职责)

- 用状态图表示一个对象再起生存期间的动态行为(要素是事件、事件后的动作、动作后的状态转移)

明确对象的创建
- 确定由谁创建对象:看被创建对象与潜在的对象创建者的关系
- 有资格创建对象
以下情况,A由B创建
B聚合了A的对象
B包含了A的对象
B记录了A对象的值
B多次引用A对象
B含有A对象初始化的数据 - 对象创建者
此表从上往下,优先级依次降低
| 场景 | 创建地点 | 创建时机 | 备注 |
|---|---|---|---|
| 唯一属于某个整体的密不可分的一部分(组合关系) (整体消亡部分就消亡) (如人与心脏,在创建人的构造方法里创建心脏) | 整体对象的属性定义和构造方法 | 整体对象的创建 | 例如,销售的业务逻辑对象由销售页面对象创建 |
| 被某一对象记录和管理(单向被关联) (被关联代表被持有引用) | 关联对象的方法 | 业务方法的执行中 对象生命周期的起始点 | 例如,连接池管理对象要负责创建连接池对象 |
| 创建所需数据被某个对象所持有(谁持有初始化数据由谁来创建) | 持有数据的对象的业务方法 | 业务方法的执行中 | 如果事先不了解创建时机,也可以是由别人创建,再由持有初始化数据的对象初始化 |
| 被某个整体傲寒(聚合关系) | 整体对象的业务方法(非构造方法) | 业务方法的执行中 | 如果某个对象有多个关联,优先选择聚合关联的整体对象;如果某个对象有多个聚合关联的整体对象,则考察整体对象的高内聚和低耦合来决定由谁创造(关联可共享,组合不共享) |
| 其他 | 通过高内聚和低耦合来决定由谁创建 | ||
| 使用此表时,从上往下匹配 |
- 例子
如上销售的例题,问由谁来创建SalesLineItem对象
Sales来创建(Sales与SalesLineItem是组合关系,而且他还知道数据)
- 创建对象的规则是为了实现高内聚低耦合
特殊场景下也会有自我创建以减少依赖的情况 - 游戏的例子
问创建Square对象、Piece对象、Player对象?
MonoplayGame在main函数中创建
Board由MonoplayGame创建
Square被Board创建
Piece由Player创建
Player由MonoplayGame创建
选择合适的控制风格
- 控制风格是Controller的模式
- 会遇到的问题:谁应该处理系统事件;场景:系统受到很多外部事件,如何组织内部处理与外部事件之间的交接
- Controller有4种方式处理外部响应
一个业务上的东西
整个系统
领域中一个模拟的角色
一个假的东西 - 举例

- 举例
一个坏的设计:展示层与逻辑层有耦合

展示层直接调用makeLineItem()方法(即创建逻辑对象的方法),这个方法可能是Sales的一个对象(逻辑层的一个处理对象),这样就造成了问题:展示层与逻辑层对象直接产生耦合
一个好的设计:对上面的改进

此例中,采用的是POST系统,Controller系统提供方法enterItem
逻辑层接口的依据:需求;需求不变,接口不变;至于交给谁来处理,是逻辑层的事情(此例中是通过Controller来调用逻辑层方法)
调用时,从展示层方法开始,通过调用中间的enterItem()将展示层与逻辑层分开
这种结构下,如果逻辑对象makeLineItem()发生了改变,那么就跟enterItem()方法及其以上就无关了(因为上层只依赖enterItem())
enterItem()方法通过调用Controller接口,来实现逻辑层上的复杂的逻辑转发调用等操作 - 创建Controller对象,使展示层代码与逻辑层代码解耦
- 接口尽量一一对应,展示层一个对象不要依赖很多逻辑对象(如果是这样,修改就很麻烦)
Controller作为控制器来解决展示层依赖很多逻辑对象问题
控制器作用是转发,这个接口根据需求确定下来后,再来解决里面的设计 - 棋类游戏的例子

思想:避免界面与逻辑直接交互,而是有一点过渡;通过过渡形成接口,形成实现后再调用
对此游戏:如果按下Dicing键,会有Controller来处理,进行操作棋盘、骰子产生随机数、操纵棋子在盘上运行等动作
处理机制也可以由游戏对象来(代替Controller),或者再创建一个裁判来处理,或者采用多个Controller…… - 两种系统协作方式:分散式、集中式
分散式:所有逻辑行为广泛分散在对象网络里
集中式:由一个Controller来记录全部逻辑 - 集中式协作
大多数逻辑都放在一个Controller中,这个Controller负责决策导向哪个功能、哪个逻辑对象来做响应
Controller需要知道更多信息来做决策和响应
这种模式下,相对来说,只要了解Controller,就能了解运转机制 - 两种协作方式等折衷方式
委托式:介于两者之间,逻辑集中在一些Controlller上 - 控制风格示意图

- 集中式控制风格的评价
容易知道在哪里决策
容易明白如何做出决策
Controller本身大而复杂 ,难以修改决策,不堪重负
逻辑越来越复杂时,维护起来很困难
Controller将其他部件当作数据仓库,这样增加模块之间耦合,破坏信息隐藏 - 集中式控制
严格集中式控制的流程图

不那么严格的集中式控制的流程图

- 实例:实现功能类似霍金的轮椅;由word产生message等
集中式控制

Controller为MessageBuilder,由他来调用各个模块
不那么集中式的控制

Controller是MessageBuilder,不管具体每个状态下做任务,只负责状态迁移(只有迁移逻辑);有一个状态机,保存所有状态和状态下的任务,他被Controller管理跳转——实现了分离MessageBuilder的逻辑联系
委托式控制

MessageBuilder是本身不在具体决定逻辑,制作协调转发(刚才还判断一下状态,现在连状态都不做了)
每个分散个体做自己的事,自己找需要委托人,之后怎么走是他们自己的事
如来了一个东西,MessageBuilder交给了Letter处理;Letter看是否构成单词,要自己去找Word;Word昨晚要找Sentence……整个过程的逻辑与MessageBuilder无关
委托式:决策逻辑更加分散 - 控制启发法
必须要有控制器:隔离展示层与逻辑层,减少耦合
Controller的不同方式意味着做隔离时,Controller用什么方式隔离
避免交互设计,大多数消息都来自于同一部件的设计
尽量保持部件小
集中式还是少用为好(力求每个部件小,简单)
每个分散的职责要符合行为职责和数据职责要在一起的原则 - 分散控制风格评价
控制流程难以理解
每个部件信息太少,耦合性增大,不利于信息隐藏 - 两种风格部件发消息常见趋势:集中式——一个发出很多(要避免);分散式——每一个连接很多
- 案例-基于委托式控制风格业务逻辑层的设计
按照包的原则分配Controller们的职责

不要找一个Controller负全责,而是分成不同业务逻辑对象的Controller
设计模型重构(后述)
为类间协作开发集成测试用例
- 测试
需求阶段结束,进行验收测试,写验收测试用例
体系结构设计阶段结束,进行集成测试,写集成测试用例,测试的是模块和模块之间的交互
详细设计结束,进行集成测试,写集成测试用例,测试类和类之间的交互 - 详细设计的集成测试
类间协作的基层测试要测:重点针对复杂逻辑(交互比较多)
自顶向下或自底向上进行集成
由于集成时类还没开发完,测试类间协作时缺少的是对象,故用Mock Object - 类间协作的集成测试

- Mock Object的定义代码
public class MockCommodity extends Commodity{ double price; public MockCommodity (double p){ price = p; } public getPrice(){ return price; } } - 集成测试代码
public class TotalIntegrationTester{ @Test public void testTotal(){ MockCommodity commodity1 = new MockCommodity(50); MockCommodity commodity2 = new MockCommodity(40); SalesLineItem salesLineItem1 = new SalesLineItem(commodity1, 2); SalesLineItem salesLineItem2 = new SalesLineItem(commodity2, 3); Sales sale = new Sales(); sale.addSalesLineItem(salesLineItem1); sale.addSalesLineItem(salesLineItem2); assertEquals(220, sale.total()); } }
结构化详细设计
- 结构化的思想
按照算法分解,从数据流图向结构图的转换 - 降低复杂度的方法
分解——同一层次上;抽象——从低层次抽象出高层次 - 如何描述一个系统
将系统分为输入、处理、输出
按算法分解来分而治之 - 按算法分解

- 从数据流图到结构图

数据流图

结构图

切分:将每个模块分成输入、处理、输出

明晰结构

最高抽象点

转化后的结构图

详细设计文档描述和评审
- 详细设计文档
中层设计:
静态结构:(通过逻辑模块、依赖等描述的职责)、接口、行为、类(类图)、依赖关系(依赖包图、供需接口等)
动态结构:顺序图描述对象交互
还要设计理由
低层设计:
重点描述关键数据结构与算法 - 详细设计验证
评审(值得花时间)
度量(模块化度量)
协作(协作测试) - CheckList

本文深入探讨详细设计的基础,包括其目的、输入与输出,以及面向对象设计中的职责与协作原则。通过实例讲解如何进行静态与动态设计模型的构建,涵盖类的职责分配、关系建立、辅助类添加及动态协作分析。












1742





