业务需求是所有架构设计的依据。架构设计必然是从需求分析开始的。
1、怎么进行逻辑架构的分析?
答:解决思路是“粗 – 细 – 粗”。首先从整体、大局、宏观的角度去思考问题,进行逻辑架构分析。
(1)粗
1)从需求文档的目录章节中分析
通过阅读目录,了解各章节、功能模块的划分,通过功能模块中的功能命名,猜测功能背后的内容;从而对整个系统 有一个整体的、直观的认识
2)从需求文档的概述部分分析
通过概述了解客户的建设目标,根据建设目标为每个功能设定优先级(不能让客户设定优先级),实现分期开发、逐步交付,从而有条不紊地开展研发工作,风险也就得到了规避。
(2)细
需求逐步细化;架构师带领着团队明确整个系统分为哪几个子系统或功能模块;明确这些子系统或功能模块包含的功能;明确每个功能的业务流程和分支;明确以什么样的界面与用户交互;最后明确这些业务包含哪些业务实体与外部接口等内容。
(3)粗
最后还要有一个由细到粗、总结归纳的过程。前面的分析都是基于客户的需求,而对软件开发来讲,客户是非专业的,后面对整个系统有了完整而清晰的认识以后,需要从专业的角度在重新思考以下问题:客户对模块的划分对吗?有的模块是不是需要拆分?客户对功能的定义对吗?有些功能是不是需要合并?
在逻辑架构的分析过程中,首先是通过用例模型的方式,由粗到细地去分析这个复杂的系统到底有哪些功能。
2.1.1 用例模型
用例模型是一套基于UML用例图进行需求分析的实践方法,它将要分析的业务系统当成一个黑盒,描述了系统到底为用户提供了哪些功能,以及这些功能到底被哪些用户使用。
在一个用例模型中通常有三种元素:用例(User Case)、参与者(Actor)、系统边界(Boundary)
用例:描述的是系统 为用户提供哪些功能,也就是系统能为用户做什么,通常被 绘制成一个椭圆。
参与者:就是使用本系统的那些人,他们按照职责被划分成不同的角色。更加广义的参与者是指那些站在系统外部,触发系统去执行某个功能的外部事物,它不仅包括角色,还包括时间与外部系统。外部系统是指在本系统之外触发本系统执行某些操作的系统。
系统边界:也就是系统要实现的功能范围,通常被绘制成一个虚线的方框。这个范围涉及软件开发的工作范围与工作量。通常情况下系统边界不用真正绘制出来,因为在用例图中被 绘制成用例的必然是系统内部的功能,被绘制成参与者的必然是系统外部事物。通过区分用例与参与者,就可以顺利地落实系统边界。
2.1.2 由粗到细的用例分析
由粗到细的用例分析就是将大问题拆分成小问题,逐步细化的过程。
不要在用例模型中为系统内部各功能的关系与流程绘制箭头。
用例与用例之间只有3种关系:包含、扩展与继承。
包含:就是将该用例中业务流程的某些相对独立的环节单独提取出来做成的用例,是原有用例的一部分,又称为“子用例”。除此之外,在用例设计时,还常常将各个功能都要使用的、共性的业务流程单独提取出来做成一个子用例。子用例通常用use或include进行描述。
扩展:就是在原有用例的基础上,在业务流程中的某个扩展点去扩展某些新功能,因而这些用例被称为“扩展用例”。这些扩展的功能是原有用例流程中分支出来的,并非必备的业务功能。
继承:体系的是一种父子关系。
参与者与参与者也有继承有关系,这是参与者之间惟一的关系。
2.1.3 用例描述
用例图只是用例分析的一个开始,还要为用例图中的每个用例编写用例描述,要细化用例。
一、业务功能 用例描述 |
||||
用例标识 |
用例名称 |
|||
创建人 |
创建日期 |
|||
版本号 |
用例类型 |
业务功能 |
||
用例描述 |
用简短的一两句话对用例的业务进行概括性的描述 |
|||
角色 |
就是参与者 |
|||
触发事件 |
参与者做了什么事情就进入该用例 |
|||
前置条件 |
参与者能够进入这个用例的前提条件 |
|||
事件流 |
基流 (主流程或成功流) |
在所有流程的成功操作的情况下,整个流程是怎么走下来的。基流实际上是一条不带任何分支的流程,通过编号来描述它们的顺序。 |
||
分支流 |
从基流分支出来,经过一个流程以后,最终还会回到基流。 通过编号来表示该分支是从基流的哪个步骤分支出来的。 |
|||
替代流 (异常流) |
是指业务的异常,即用户进行了错误的操作,或进入某种错误的状态,因此分支出来,经过一段处理就直接结束 了,而不会回到基流。 通过编号来表示该分支是从基流的哪个步骤分支出来的。替代流在分支出来的时候,还要描述是什么条件触发的分支。 |
|||
后置条件 |
在完成一系列事件流以后应达到的目标、可以完成的操作以及可以进行哪些后续操作 |
|||
非功能需求 |
||||
备注 |
优先级 |
业务需求列表 |
|||
创建人 |
版本 |
描述 |
创建日期 |
二、查询报表 用例描述 |
|||
用例标识 |
用例名称 |
||
创建人 |
创建日期 |
||
版本号 |
用例类型 |
业务功能 |
|
用例描述 |
用简短的一两句话对用例的业务进行概括性的描述 |
||
角色 |
就是参与者 |
||
报表作用 |
|||
过滤条件 |
|||
输出字段 |
|||
计算公式 |
|||
数据链接 |
|||
数据来源 |
|||
非功能需求 |
|||
备注 |
优先级 |
||
业务需求列表 |
|||
创建人 |
版本 |
描述 |
创建日期 |
三、图表展示 用例描述 |
|||
用例标识 |
用例名称 |
||
创建人 |
创建日期 |
||
版本号 |
用例类型 |
业务功能 |
|
用例描述 |
用简短的一两句话对用例的业务进行概括性的描述 |
||
角色 |
就是参与者 |
||
图表内容 |
|||
过滤条件 |
|||
数据频率 |
|||
数据链接 |
|||
数据来源 |
|||
非功能需求 |
|||
备注 |
优先级 |
||
业务需求列表 |
|||
创建人 |
版本 |
描述 |
创建日期 |
2.1.4 业务需求列表
业务需求列表,又称为需求跟踪表,是用户对业务需求的最原始描述。
(1)需求列表不掺杂我们对业务需求的任何分析与设计,这是需求列表的核心,也是它存在的意义。
(2)需求列表应当是站在业务人员视角的,是对业务需求的简明扼要的描述。
(3)需求列表中应当剔除那些客户对界面、对设计方面的要求。
这些内容不能成为验证系统功能的基石,因而不应当写入需求列表中。需求列表描述的更应当是客户对软件功能的意图,即这个功能要解决用户的什么业务痛点。
(4)在系统 需求日后变更中,每次变更都先将变更写入需求列表中,然后再变更与设计。
2.1.5 需求规格说明书
需求规格说明书分为用户需求规格说明书和产品需求规格说明书。用户需求规格说明书是站在用户角度描述的系统业务需求,用于与用户签字确认业务需求;产品需求规格说明书是站在开发人员角度描述的系统业务需求,是指导开发人员完成设计与开发的技术性文档。
需求规格说明书是敏捷团队在“轻文档”研发过程中必须要编写的文档。
2.2 界面原型分析
在需求分析的时候,我们跟用户常常是在“空对空”地讨论问题。用户不是专业人士,他搞不清楚软件到底会做成什么样,所以你跟他确认的时候他就认可了。但是,当软件上线后,他看到实物,知道软件做成什么样子了,就开始不满意提出变更。
快速原型法,简称原型法,旨在解决在需求确认时说得很好,一到软件上线后就这不对,那也不对的问题(当我看到了我就开始变更了)。
原型法的关键就是快速开发出原型,交给用户去试用、去补充、去修改;其目的就是模拟客户的需求,用来与客户进行确认。
1、软件退化的根源是什么?
答:软件的本质就是对真实世界的模拟,但是,在软件越来越接近真实世界的过程中,业务逻辑也会变得越来越复杂,软件规模越来越庞大,逐渐由一个简单软件转变成一个复杂软件。如果每次软件变更时适时地进行解耦和功能扩展,再实现新的功能,就能保持高质量的软件设计;如果在每次软件变更时没有调整程序结构,而是在原有的程序结构上不断地塞代码,软件就会退化。所以,软件变更不是软件退化的根源,而只是一个诱因;软件变更后,不对程序结构进行优化调整,才是软件退化的根源。
2、什么是开闭原则(OCP)?
答:开闭原则是面向对象设计中“可复用设计”的基石,是面向对象设计中最重要的原则之一,其它很多的设计原则都实现开闭原则的一种手段。
开闭原则的定义:一个软件实体如类,模块和函数应该对扩展开放,对修改关闭。
开闭原则明确的告诉我们:软件实现应该对扩展开放,对修改关闭,其含义是说一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化的。那什么是软件实体呢?
软件实体包括以下几个部分:
(1)项目或软件产品中按照一定的逻辑规则划分的模块
(2)抽象和类
(3)方法
一个软件产品只要在生命周期内,都会发生变化,即然变化是一个事实,我们就应该在设计时尽量适应这些变化,以提高项目的稳定性和灵活性,真正实现“拥抱变化”。开闭原则告诉我们应尽量通过扩展软件实体的行为来实现变化,而不是通过修改现有代码来完成变化,它是为软件实体的未来事件而制定的对现行开发设计进行约束的一个原则。
3、如何使用开闭原则?
答:
(1)抽象约束
抽象是对一组事物的通用描述,没有具体的实现,也就表示它可以有非常多的可能性,可以跟随需求的变化而变化。因此,通过接口或抽象类可以约束一组可能变化的行为,并且能够实现对扩展开放,其包含三层含义:
1)通过接口或抽象类约束扩散,对扩展进行边界限定,不允许出现在接口或抽象类中不存在的public方法。
2)参数类型,引用对象尽量使用接口或抽象类,而不是实现类,这主要是实现里氏替换原则的一个要求抽象层尽量保持稳定,一旦确定就不要修改
(2)元数据(metadata)控件模块行为
编程是一个很苦很累的活,那怎么才能减轻压力呢?答案是尽量使用元数据来控制程序的行为,减少重复开发。什么是元数据?用来描述环境和数据的数据,通俗的说就是配置参数,参数可以从文件中获得,也可以从数据库中获得。
(3)制定项目章程
在一个团队中,建立项目章程是非常重要的,因为章程是所有人员都必须遵守的约定,对项目来说,约定优于配置。这比通过接口或抽象类进行约束效率更高,而扩展性一点也没有减少。
(4)封装变化
对变化封装包含两层含义:
1)将相同的变化封装到一个接口或抽象类中
2)将不同的变化封装到不同的接口或抽象类中,不应该有两个不同的变化出现在同一个接口或抽象类中。
封装变化,也就是受保护的变化,找出预计有变化或不稳定的点,我们为这些变化点创建稳定的接口。
4、什么是两顶帽子的设计方式?
答:
(1)在不添加新功能的前提下,重构代码,调整原有程序结构,以适应新功能。
(2)实现新的功能。
Kent Beck提出了“两顶帽子”的比喻。使用重构技术开发软件时,我把自己的时间分配给两种截然不同的行为:添加新功能和重构。添加新功能时,我不应该修改既有代码,只管添加新功能。通过添加测试并让测试正常运行,我可以衡量自己的工作进度。重构时我就不能再添加功能,只管调整代码的结构。此时我不应该添加任何测试(除非发现有先前遗漏的东西),只在绝对必要(用以处理接口变化)时才修改测试。
即,戴上一顶帽子后,就进入当前帽子的角色,从当前帽子的视角去考虑、处理问题;不要过度设计。
5、领域驱动设计的思想是什么?
答:将软件设计与真实世界对应起来,因为软件的本质是对真实世界的模拟。
在每次需求变更的时候,将变更还原到真实世界中,看看真实世界是什么样子的,根据真实世界进行变更;这样不论怎么变更 ,都不会迷失方向,从而设计质量也得到保证。
6、将真实世界与软件世界对应起来,要对应哪些内容?
答:
(1)真实世界有什么事物,软件世界就有什么对象
(2)真实世界中这些事物都有哪些行为,软件世界中这些对象就有哪些方向
(3)真实世界中这些事物都有哪些关系,软件世界中这些对象间就有什么关联
7、什么是单一职责原则(SRP)?
答:单一职责原则即一个类应该有且只有一个变化的原因。
软件系统中的每个元素只完成自己职责范围内的事,而将其他的事交给别人去做,自己只是去调用。
单一职责原则要求我们在维护软件的过程中不断地进行整理,将因同一个原因而变更的代码放在一起,将因不同原因而变更的代码分开放。按照这样的设计原则,实现了维护成本最低的目的。
8、什么是策略模式?
答:在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。
在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。
意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
主要解决:在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。
何时使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。
如何解决:将这些算法封装成一个一个的类,任意地替换。
关键代码:实现同一个接口。
应用实例: 1、诸葛亮的锦囊妙计,每一个锦囊就是一个策略。 2、旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。
优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。
使用场景: 1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。 2、一个系统需要动态地在几种算法中选择一种。 3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
注意事项:如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。
实现:
我们将创建一个定义活动的 Strategy 接口和实现了 Strategy 接口的实体策略类。Context 是一个使用了某种策略的类。
StrategyPatternDemo,我们的演示类使用 Context 和策略对象来演示 Context 在它所配置或使用的策略改变时的行为变化。
1、架构师如何同客户进行需求确认?
答:
(1)跳出需求去分析需求相关的业务
(2)分析需求背后的动机
(3)基于需求动机去制订可行的方案