前面讲了为什么我们要使用分析模型,现在我们看设计分析模型的基本原则。
分配职责和职责驱动设计
我们在开始分析模型的时候,首先要弄清楚一个非常重要的原则,就是以职责为中心。OO分析设计的核心原则之一,就是软件系统中的所有元素都必须具有高度相关的职责,也就是说,软件系统中所有的模块、包、对象类,都应当拥有一个清晰的职责,并且与它相关的所有元素(即模块中的所有包、包中的所有对象类、对象类中的所有属性和行为)都必须与这个职责具有高度的相关性。因此,分析模型的首要设计原则就是职责驱动设计(Responsibility-Drive Design)。依据职责驱动设计的原则,我们在分析系统的时候,必须为每一个模块、包、对象类,特别是对象类,定义一个明确的职责。在这个原则下,我们最核心的问题是,我们在定义每一个对象类时都应当为其定义职责,在定义它的行为和属性时,都应当与其职责高度相关。
如何进行职责驱动设计呢?大师Craig Larman在他的经典著作《UML和模式应用》中,提出了GRASP软件设计模式。GRASP(General Responsibility Assignment Software Patterns),翻译过来就是通用职责分配设计模式,它包括创建者(Creator)、控制器(Controller)、信息专家(Information Expert)、低耦合(Low Coupling)、高内聚(High Cohesion)、多态(Polymorphism)、纯虚构(Pure Fabrication)、间接性(Indirection)和防止变异(Protected Variations)九部分。GRASP这九部分,与其说是模式,不如说是原则更加准确。现在跟大家探讨一下低耦合、高内聚、信息专家和创建者四个原则。
1.低耦合与高内聚
这两个词可能是你早已耳熟能详的了吧,我们在看spring的书籍、MVC的数据、设计模式的书籍,无处不提到“低耦合、高内聚”,它已经成为软件设计质量的重要标准之一。那么,什么是低耦合,什么又是高内聚呢?
耦合就是对某元素与其它元素之间的连接、感知和依赖的量度。这里所说的元素,即可以是功能、对象类,也可以指系统、子系统、模块。假如一个元素X去连接元素Y,或者通过自己的方法可以感知Y,或者当Y不存在的时候就不能正常工作,那么就说元素X与元素Y耦合。那么,哪些是耦合呢?
1.元素Y是元素X的属性,或者元素X引用了元素Y的实例(这包括元素X调用的某个方法,其参数中包含元素Y)。
2.元素X调用了元素Y的方法。
3.元素X直接或间接成为元素Y的子类。
4.元素X是接口Y的实现。
耦合带来的问题是,当元素Y发生变更或不存在时,都将影响元素X的正常工作,影响系统的可维护性和易变更性。同时元素X只能工作于元素Y存在的环境中,这也降低了元素X的可复用性。正因为耦合的种种弊端,我们在软件设计的时候努力追求“低耦合”。低耦合就是要求在我们的软件系统中,某元素不要过度依赖于其它元素。请注意这里的“过度”二字。系统中低耦合不能过度,比如说我们设计一个类可以不与JDK耦合,这可能吗?除非你不是设计的Java程序。再比如我设计了一个类,它不与我的系统中的任何类发生耦合。如果有这样一个类,那么它必然是低内聚。耦合与内聚常常是一个矛盾的两个方面,内聚要求类与类之间出现耦合,过低的耦合必然造成过低的内聚。最佳的方案就是寻找一个合适的中间点。那么,什么是内聚呢?
内聚,更为专业的说法叫功能内聚,是对软件系统中元素职责相关性和集中度的度量。如果元素具有高度相关的职责,除了职责范围内的任务,没有其它过多的工作,那么该元素就具有高内聚性,反之则为低内聚性。高内聚要求软件系统中的各个元素具有较高的协作性,因为在我们在完成软件需求中的一个功能,可能需要做各种事情,但是具有高内聚性的一个元素,只完成它职责范围内的事情,而把那些不在它职责范围内的事情拿去请求别人来完成。这就好像,如果我是一个项目经理,我的职责是监控和协调我的项目各个阶段的工作。当我的项目进入需求分析阶段,我会请求需求分析员来完成;当我的项目进入开发阶段,我会请求软件开发人员来完成……如果在开发阶段,我做了开发工作,我就不是一个高内聚的元素,因为开发工作不是我的职责。
高内聚从本质上提高了软件的可读性、可复用性和可维护性,因此,高内聚已经成为软件设计中的一种基本品质。但是,高内聚从内在要求软件中的所有元素必须充分协作,从而必然造成元素之间的相互感知,也就是相互耦合。高内聚与低耦合成为了一对矛盾,就要求我们在设计过程中必须寻找那个最佳的中间点,既满足高内聚,又能很好的低耦合。
2.信息专家
前面我们说了,我们分析和设计系统的基本原则是职责驱动设计,那么职责分配的原则是什么呢?信息专家模式回答了我们。信息专家模式(又称为专家模式)告诉我们,在OO分析中,应当将职责分配给软件系统中的这样一个对象类,它拥有实现这个职责所必须的信息。我们称这个对象类叫“信息专家”。
在一个软件系统中有许多的功能,每个功能是由无数的行为共同协作完成的。而职责是一组高度相关的行为的集合。我们把软件系统中一组组高度相关的行为归集为一个个的职责,并且将每个职责分配给这个职责相应的信息专家,既可以实现高内聚,又可以实现低耦合。为什么这样说呢?将一组高度相关的行为分配给一个对象类,其本身就是高内聚。而为了完成某个职责,必须访问那些实现这个职责所需的信息。如果这个职责没有分配给信息专家,势必造成因访问这些信息时产生的耦合;反之,如果将这个职责分配给信息专家,这种访问信息而产生的耦合就随之消失,从而降低了系统的耦合度。
信息专家所表达的道理虽然浅显,但在我们的项目中,没有遵照信息专家模式而出现的糟糕设计随处可见,拿一个我经历过的实例来说吧。我曾经参与开发过一个公司内部评审系统。这个系统分为三个用例:制订评审计划、执行评审(执行评审在软件中的表现就是填写评审表)、制作评审报告。在制订评审计划时,需要详细填写评审的内容、参与的评委。在执行评审的时候,每个评委需要为所有评审的内容,在各自的评审表中填写评审意见。根据以上的需求,为了实现“执行评审”这个用例,我们势必要做“读取评审内容”和“读取所有评委”这两个行为。现在的问题是,这两个行为应当分配给谁?稍加分析我们可以发现,评审内容和评委是评审计划的一部分,因此“读取评审内容”和“读取所有评委”都是“读取评审计划”这个职责的所在范围。按照信息专家模式的要求,“评审计划”类拥有评审计划的所有信息,因此应当将“读取评审计划”这个职责分配给“评审计划”类,即“读取评审内容”和“读取所有评委”都是“评审计划”类的行为。然而,现实却不是这样。
制订评审计划、执行评审和制造评审报告被分配给了三个程序员。完成“执行评审”任务的程序员甲,为了完成他的功能必须执行“读取评审内容”和“读取所有评委”的行为,他应当要求完成“制订评审计划”的程序员乙来实现这两个行为,并且为程序员甲调用。但是,程序员乙认为自己已经很忙了,为什么要答应程序员甲的要求呢?正因为如此,程序员乙没有在“评审计划”类中实现“读取评审内容”和“读取所有评委”的行为,而程序员甲只能在“评审表”中实现这两个行为,也就是按照事先约定好的逻辑,直接读取数据库。当程序员乙因为某个业务变更修改了“评审计划”的业务逻辑或表结构时,直接导致的就是“执行评审”的功能无法执行。其根本原因就是“评审表”类执行了与它职责无关的读取评审计划中相关信息的工作,从而造成与“评审计划”的业务耦合。这样的问题在软件开发项目中非常常见,解决的办法应当是,程序员开始编程前,应当有个分析员对整个系统进行规划。有了分析员的规划,自然会将“读取评审内容”和“读取所有评委”的行为分配给“评审计划”。
3.谁来创建我
当我们分析清楚客户需求设计出用例模型以后,当我们分析清楚客户的业务环境制作出领域模型以后,当我们综合用例模型、领域模型和我们的聪明才智设计出一个又一个的类和它们各自的方法以后,当就在一切都准备就绪只欠东风的关键时刻,一个对象发出了撕心裂肺的怒吼——谁来创建我?!!!一个对象,不管拥有多么强大的功能,不管进行了多么精巧的设计,如果不能被创建,就如同韩信不能做将军,孙膑不能当军师,勾践不能回越国,刘备不能得荆州,一切一切的雄才武略都如废纸一张。既然“创建”对于对象如此重要,我们就来好好探讨一下GRASP中关于对象创建的问题。
创建对象是面向对象系统中常见的活动之一,然而创建往往伴随着耦合。我们应当追求低耦合,但是又必须要创建对象。怎么办呢?最佳的办法就是让那些必须与创建的对象耦合的类,去完成创建的工作。创建者模式为我们提出了以下建议:
如果要将创建A的职责分配给B,那么应当满足以下条件(越多越好):
l B包含或聚合A
l B记录A
l B调用A
l B拥有A的初始化数据,并且在创建A的时候要传递给A,即B是A的专家。
如果有多个类满足以上条件,首选满足条件1或2的类。从以上条件可以看出,即使B没有创建A,B已经与A耦合了,所以B创建了A,也不会提高系统的耦合度,因此是最佳的选择。
但是,在软件系统中还有许多的例外情况不按照创建者模式创建对象。当创建一个对象,或者与它相关的整个聚合,其业务逻辑变得非常复杂时,为了不过多地暴露其内部结构,保证系统的封装性,可能选择工厂模式创建对象。在这种情况下,可能涉及到创建对象的复杂组装过程(这个问题还会在后面继续讨论)。另一种情况是,系统中的服务类对象,为了提高系统运行效率而采用单例的方式,为其它对象提供服务。像这样的对象类,我们往往采用,在系统启动,或第一次有对象访问它们时,由系统对其进行创建,例如spring框架中的那些bean。
分享到:
- 2009-10-16 11:14
- 浏览 3818
- 评论(0)
- 论坛回复 / 浏览 (0 / 2452)
- 查看更多
相关推荐
分析模型有助于识别系统的职责和组件,为设计模型提供基础。 5. 设计模型:设计模型是将分析模型转化为具体实现的蓝图,包括类图、接口图和组件图等。设计模型强调结构和组织,指导开发者如何实施代码。 在软件...
#### 四、案例分析 假设有一个物联网传感器节点,其主要功能是在指定的时间间隔内采集环境温度并上传至云端服务器。为了实现低功耗设计,可以采取以下措施: 1. **硬件设计优化** - 使用低功耗型MCU作为核心...
在探讨Windows 8降级那些事儿时,首先要明确的是,微软在Windows 8的最终用户许可协议(EULA)中确实赋予了用户一项特殊权利,那就是降级权。这意味着购买预装了Windows 8专业版的用户有权限将操作系统降级至Windows...
接着,我们谈谈CQRS(Command Query Responsibility Segregation,命令查询职责分离)。这是一种架构模式,将读取和写入操作分离,以提高系统的性能和可维护性。在C# 2008中,可以创建独立的查询和命令处理器,分别...
在"linux那些事儿"中,你可能会学到如何编写和调试驱动程序,包括理解设备模型、I/O操作、中断处理、设备文件和系统调用等关键概念。驱动开发需要对C语言有扎实的基础,以及对操作系统原理的理解,尤其是内存管理、...
根据毕业时间的不同一般集中在5、6月份和9、10月份,那么最近一段时间也是学校毕业答辩的高峰时期,作为检验论文是否成功的最后一道关卡,有很多学生都不知道应该如何准备,今天我们就谈谈论文答辩那些事儿~ ...
- **安全标准**:随着ISO26262安全标准的广泛应用,预驱动器IC的安全性成为了设计中的重要考量因素之一,需要专门的监测机制确保IC的运行健康。 #### 内燃发动机中的传感技术 在当代内燃发动机中,传感技术的应用...
首先,我们来谈谈“按键与LED驱动程序设计”。在嵌入式系统中,输入设备如按键和输出设备如LED是与用户交互的基础。按键驱动程序涉及处理硬件中断、识别按键按下和释放事件,以及将这些事件转化为可被操作系统或应用...
PKPM(中国建筑科学研究院PKPM系列软件)是一款在建筑工程领域广泛应用的专业软件,它涵盖了结构设计、建筑施工图绘制、能耗分析等多个方面。"PKPM全套狗驱动"指的是该软件的全套加密狗驱动程序,这些驱动是确保PKPM...
这种层次化的设计使得设备的管理和驱动程序的编写更为有序。 设备模型还引入了设备树(Device Tree)的概念,特别是在嵌入式系统中。设备树是一个描述硬件配置的数据结构,它提供了一种灵活的方式来指定硬件布局,...
谈谈农村学校如何才能创新驱动.doc
在建筑节能设计部分,新驱动提供了更丰富的参数设置和模拟分析,帮助设计师实现绿色建筑的目标;在施工管理模块,新驱动增加了进度计划、成本控制等实用工具,便于项目管理人员进行精细化管理。 接下来,我们来谈谈...
16 编排其实很简单:谈谈“控制器”模型.pdf
需要注意的是,尽管这些驱动设计得可以直接安装,但在实际操作中,有时可能会因为系统版本、硬件兼容性或其他未知因素导致安装失败。如果遇到这种情况,不要慌张,可以尝试更新操作系统、检查硬件配置或寻找技术支持...
在本压缩包文件"戴尔阵列卡驱动s100 s300 2008 R2.rar"中,包含了适用于戴尔阵列卡S100和S300的驱动程序,这些驱动程序是针对Windows Server 2008 R2操作系统设计的。 首先,让我们深入了解戴尔阵列卡S100和S300: ...
这篇报告主要围绕着2012年9月26日民生证券发布的关于商贸行业的分析,重点探讨了电子商务在物流、资金流以及信息流方面的重要作用和挑战。在电子商务的快速发展背景下,这三大流是电商运营的核心组成部分,对于理解...
这些要素是电商运营的关键组成部分,通过全面分析这些要素,可以深入了解电商行业的运作机制和未来发展趋势。 首先,物流环节是电商运营中至关重要的一环。由于电商与传统零售供应链在管理方式和运作模式上存在较大...
Java数据库驱动,通常被称为JDBC(Java Database Connectivity)驱动,是Java编程语言中连接到数据库的关键组件。在Java中,JDBC驱动程序提供了一个标准的API,使得开发人员能够使用SQL语句与各种数据库进行交互。这...