[课业] 21 | 软工 | 详细设计

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

详细设计基础

什么是详细设计

  1. 体系结构设计表达系统高层设计抽象——表达模块之间的交互、接口等
    详细设计落实到具体的模块之中去,(面向对象的详细设计下)模块中的对象、类是如何设计的
  2. 详细设计是模块内部具体设计机制
    中层上描述:类的规格(类接口、类之间如何交互等)
    低层上描述:数据结构、算法等(类的实现、方法实现)
  3. 详细设计也是一种设计,也需要设计师考虑美学、功能和其他的一些重要属性(如可修改性、可维护性等)

详细设计的出发点

详细设计的输入为:需求规格说明与体系结构设计(到这个阶段我们手里有的)

详细设计的上下文

上下文即指详细设计的输入、输出都是什么

建桥的案例

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

详细设计的上下文的思考

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

  4. 软件体系结构设计工作中可能被用到的
    构建之间的接口
    //被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;
    	...
    }
    
  5. 详细设计的输出
    输出的是模块内部的实现(包内部、模块内部)(还会有一些繁殖循环依赖的设计)

面向对象的详细设计

面向对象设计的思想

职责

  1. 职责就是义务
    职责包括做任务、维护数据等工作
    做任务——行为职责——对应类对象的成员方法
    维护数据——数据职责——对应类对象的成员变量
    数据和行为被封装,一起体现职责
  2. 职责驱动、职责分配
    职责的说明可以是不同抽象层次上的
    职责可以被分解:大职责可以分解成小职责,由小职责合作形成大职责
    高层次职责(大职责)对应大的抽象部件;小职责对应小的抽象部件
    职责的分解可以指示部件的分解
  3. 职责驱动和职责分配的工作:将系统职责一点点地从上到下分配
    操作方式:自顶向下、自底向上、两种结合都可以,都算职责驱动
  4. 职责分配和职责驱动的注意事项
    一个更高的原则:职责分配时,要做到高内聚,低耦合(每个职责本身高内聚,职责之间交互要少,即低耦合)
    模块职责不可以重叠
    职责的数据与行为放在一起,共同体现一个职责
  5. 代理(Delegation)
    面向对象的职责处理中,还会有代理机制
    代理机制应该更多地被使用,要做一件事时,如果有模块声称自己有此职责,直接交给该模块处理就行(自己就不用管了)

协作

  1. 什么是协作
    协作完成更大职责;一个程序中的对象必须协作,这样他们才能完成更大职责,如果没有协作的话就只能写出一个做所有工作的对象
    将系统与社会类比,对象相当于社会个体,协作可以视为个体之间的协作(模拟社会分工)
    通过协作,应用被分解为不同行为,不同行为由不同的对象完成,每次协作都可以实现或多或少的系统功能
    面向对象的应用是一个关系网络,每个个体职责清晰,只要搞好自己的事情就好;社会的复杂性落在了网络之间的关联上
    结构化编程应用:清晰的关系结构和交互,复杂的方法
    机制层面上,协作是通过发消息完成的
    语言层面上,协作是通过方法调用完成的
  2. 协作的重要性
    通过协作完成目标功能
    如果协作设计的不好,系统容易出现问题

面向对象详细设计的过程

概述

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

设计模型建立

通过职责建立静态设计模型
抽象对象的职责
  1. 类表达了对对象族本质特征的抽象
    一个类对应多个对象,他们的共有特征(属性、方法等)
  2. 构建的蓝图
  3. 职责:数据职责、行为职责
  4. 单一类图示例

    -代表私有,+代表公开
    类图三部分:类名,属性,方法
    方法最后的是返回值类型
抽象类之间的关系
关系
  1. 类之间的关系
关系关系短语解释多重性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类实现接口,必须实现接口中所有的方法
空心三角箭头一侧指向抽象那一方,即接口
类之间关系
注:多重性简单来说是指两个类“一对多还是多对多还是多对一还是别的”
  1. 类图示例
GRASP模式
  1. GRASP模式是一种更加高层抽象而基础的原则,致力于指导如何将职责分配给类
  2. GRASP模式:
    低耦合(后述)、高内聚(后述)、信息专家、创建者、控制器
  3. 基本原则:要选内聚性好的,耦合性好的,稳定性好的
    决策时,面对多个选择,考虑未来变化,即可修改性
  4. 信息专家
    面向对象设计分配职责时,依据是:哪个类拥有实现职责必须的信息,就将这个职责分配给哪个类
    如果有知道职责100%信息的类,那么就将职责分配给这个类
    如果两个类对该职责信息知晓比例是90% vs. 10%,那么倾向于给90*信息的那个人
    如果是50% vs. 50%,就看这个职责放在哪个类里合适(因为往类里添加职责是对类有影响的):看看加入某个类后,类的意义是否被破坏(如下面的销售和商品的例子中,如果商品类中有了计算小记价格的功能,商品类就被破坏了);还可要看关联的方向(谁持有谁的引用)(如下面商品的例子,是SalesItem持有商品的引用而不是商品持有SalesItem的引用;即看是互相持有还是一方持有另一方)
  5. 信息专家的案例

哪个类应该分配到“计算一次销售的总价格”的职责(Sales还是SalesLineItem)

由图可知:Sales持有所有SalesLineItem对象,含有计算总价的所有信息;SalesLineItem可以计算小记但是不能得到总价
即Sales是该职责的信息专家,职责应该分给Sales类


通过遍历计算总价

  1. 信息专家的原理
    保持信息的封装,有利于低耦合、高内聚
  2. 只能热水器的例子

智能控制水温:特定时间水温会有明确的该高还是该低规则
概念模型

怎么知道当前时间是该升温还是降温:三种方法

  • 方法一;Controller自己保存特殊事件并计算(比较当前时间和特殊时间)

    这样的Controller类,low、high只与do()有关,specialTime只与judge()有关,这样就是一个类内多个职责,不好
  • 方法二:由SpecialTime类保存特殊时间;Controller调用getSpecialTie()得到特殊时间,再计算

    此时,SpecialTime类是数据提供方
    这样,判断是否是特殊时间的职责所需信息在SpecialTime类里,而职责方法在Controller类里,即数据与行为分离,不好
  • 方法三:由SpecialTime类保存特殊时间,并提供isSpecialTime();Controller调用方法

    此时,Controller调用isSpecialTime()方法,直接得到结果;这是委托SpecialTime类来做判断;SpecialTime类被委托
    这个是好的


    思考:为何这个例子中getSpecialTime()的方案(数据提供机制)不合适,而Sales例子中getSalesItem()可以?
    解答:如果职责全给商品,商品也算不出来(即二者都不是100%信息专家,数据提供不可避免)
添加辅助类
  1. 辅助类:设计中出现的类而前面过程没出现的类
    接口类、记录类/数据类(记录:专门用来存储信息的对象,可能是持久化对象PO)、启动类(负责系统启动)、控制器类(作为一个Controller,起协调、转发的作用,也业务无关而与系统有关,进行对系统实现的控制,所以才在添加辅助类阶段添加这个类)、实现数据类型的类、容器类等
  2. 辅助类与业务无关,但是需要他们的辅助使系统运作起来
  3. 添加辅助类后的设计模型:更加丰满
通过协作建立动态设计模型

这个阶段看的是运行起来的样子

抽象对象之间协作
  1. 抽象协作的方法:建立大小职责的联系;跨过鸿沟,建立起职责网络
    从大到小,将大职责分配给各个小对象
    从小到大,将对象的小职责聚合形成大职责(每个类都是小职责)
  2. 用顺序图表示对象之间的协作(对象之间通过消息传递完成大职责)
  3. 用状态图表示一个对象再起生存期间的动态行为(要素是事件、事件后的动作、动作后的状态转移)
明确对象的创建
  1. 确定由谁创建对象:看被创建对象与潜在的对象创建者的关系
  2. 有资格创建对象
    以下情况,A由B创建
    B聚合了A的对象
    B包含了A的对象
    B记录了A对象的值
    B多次引用A对象
    B含有A对象初始化的数据
  3. 对象创建者
    此表从上往下,优先级依次降低
场景创建地点创建时机备注
唯一属于某个整体的密不可分的一部分(组合关系)
(整体消亡部分就消亡)
(如人与心脏,在创建人的构造方法里创建心脏)
整体对象的属性定义和构造方法整体对象的创建例如,销售的业务逻辑对象由销售页面对象创建
被某一对象记录和管理(单向被关联)
(被关联代表被持有引用)
关联对象的方法业务方法的执行中 对象生命周期的起始点例如,连接池管理对象要负责创建连接池对象
创建所需数据被某个对象所持有(谁持有初始化数据由谁来创建)持有数据的对象的业务方法业务方法的执行中如果事先不了解创建时机,也可以是由别人创建,再由持有初始化数据的对象初始化
被某个整体傲寒(聚合关系)整体对象的业务方法(非构造方法)业务方法的执行中如果某个对象有多个关联,优先选择聚合关联的整体对象;如果某个对象有多个聚合关联的整体对象,则考察整体对象的高内聚和低耦合来决定由谁创造(关联可共享,组合不共享)
其他通过高内聚和低耦合来决定由谁创建
使用此表时,从上往下匹配
  1. 例子

如上销售的例题,问由谁来创建SalesLineItem对象
Sales来创建(Sales与SalesLineItem是组合关系,而且他还知道数据)

  1. 创建对象的规则是为了实现高内聚低耦合
    特殊场景下也会有自我创建以减少依赖的情况
  2. 游戏的例子

问创建Square对象、Piece对象、Player对象?

MonoplayGame在main函数中创建
Board由MonoplayGame创建
Square被Board创建
Piece由Player创建
Player由MonoplayGame创建

选择合适的控制风格
  1. 控制风格是Controller的模式
  2. 会遇到的问题:谁应该处理系统事件;场景:系统受到很多外部事件,如何组织内部处理与外部事件之间的交接
  3. Controller有4种方式处理外部响应
    一个业务上的东西
    整个系统
    领域中一个模拟的角色
    一个假的东西
  4. 举例
  5. 举例
    一个坏的设计:展示层与逻辑层有耦合

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

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

    思想:避免界面与逻辑直接交互,而是有一点过渡;通过过渡形成接口,形成实现后再调用
    对此游戏:如果按下Dicing键,会有Controller来处理,进行操作棋盘、骰子产生随机数、操纵棋子在盘上运行等动作
    处理机制也可以由游戏对象来(代替Controller),或者再创建一个裁判来处理,或者采用多个Controller……
  9. 两种系统协作方式:分散式、集中式
    分散式:所有逻辑行为广泛分散在对象网络里
    集中式:由一个Controller来记录全部逻辑
  10. 集中式协作
    大多数逻辑都放在一个Controller中,这个Controller负责决策导向哪个功能、哪个逻辑对象来做响应
    Controller需要知道更多信息来做决策和响应
    这种模式下,相对来说,只要了解Controller,就能了解运转机制
  11. 两种协作方式等折衷方式
    委托式:介于两者之间,逻辑集中在一些Controlller上
  12. 控制风格示意图
  13. 集中式控制风格的评价
    容易知道在哪里决策
    容易明白如何做出决策
    Controller本身大而复杂 ,难以修改决策,不堪重负
    逻辑越来越复杂时,维护起来很困难
    Controller将其他部件当作数据仓库,这样增加模块之间耦合,破坏信息隐藏
  14. 集中式控制
    严格集中式控制的流程图

    不那么严格的集中式控制的流程图
  15. 实例:实现功能类似霍金的轮椅;由word产生message等
    集中式控制

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

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

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

    不要找一个Controller负全责,而是分成不同业务逻辑对象的Controller

设计模型重构(后述)

为类间协作开发集成测试用例

  1. 测试
    需求阶段结束,进行验收测试,写验收测试用例
    体系结构设计阶段结束,进行集成测试,写集成测试用例,测试的是模块和模块之间的交互
    详细设计结束,进行集成测试,写集成测试用例,测试类和类之间的交互
  2. 详细设计的集成测试
    类间协作的基层测试要测:重点针对复杂逻辑(交互比较多)
    自顶向下或自底向上进行集成
    由于集成时类还没开发完,测试类间协作时缺少的是对象,故用Mock Object
  3. 类间协作的集成测试
  4. Mock Object的定义代码
    public class MockCommodity extends Commodity{
    	double price;
    	public MockCommodity (double p){
    		price = p;
    	}
    	public getPrice(){
    		return price;	
    	}
    }
    
  5. 集成测试代码
    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());
    	}
    }
    

结构化详细设计

  1. 结构化的思想
    按照算法分解,从数据流图向结构图的转换
  2. 降低复杂度的方法
    分解——同一层次上;抽象——从低层次抽象出高层次
  3. 如何描述一个系统
    将系统分为输入、处理、输出
    按算法分解来分而治之
  4. 按算法分解
  5. 从数据流图到结构图

    数据流图

    结构图

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

    明晰结构


最高抽象点

转化后的结构图

详细设计文档描述和评审

  1. 详细设计文档
    中层设计:
    静态结构:(通过逻辑模块、依赖等描述的职责)、接口、行为、类(类图)、依赖关系(依赖包图、供需接口等)
    动态结构:顺序图描述对象交互
    还要设计理由
    低层设计:
    重点描述关键数据结构与算法
  2. 详细设计验证
    评审(值得花时间)
    度量(模块化度量)
    协作(协作测试)
  3. CheckList
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值