写给自己看的小设计5 - 对象设计过程之设计目标

  浏览完代码设计中对象设计的核心准则和扩展原则后,最后我们再回头看看另外一个原则不像原则,规则不像规则,模式不像模式,实践不像实践的四不像原则 - GRASP原则。

  说到对象设计原则,就不能不提GRASP原则,但是从我来看,这个原则完全不是原则,而是设计过程。
  GRASP,全称为General Responsibility Assignment Software Pattern,即通用职责分配软件模式,它由《UML和模式应用》(Applying UML and Patterns)一书作者Craig Larman提出。它是站在面向对象设计的角度,告诉我们怎样设计问题空间中的类与分配它们的行为职责,以及明确类之间的相互关系等,它不是像GoF设计模式一样是针对特定问题而提出的解决方案,而是站在一个更高的角度来看待对象的设计问题。
  GRASP是对象职责分配的基本原则,其核心思想是职责分配,用职责来驱动对象设计。它定义了9个基本的OO设计原则或基本的设计构件。这9个设计模式分别是:创建者(Creator)、信息专家(Information Expert)、低耦合(Low Coupling)、控制器(Controller)、高内聚(High Cohesion)、多态性(Polymorphism)、纯虚构(Pure Fabrication)、间接访问(Indirection)、防止变异(Protected Variations)。对于这9个方面的细节,我这里就不详细说明了,感兴趣的同学直接百度/谷歌"GRASP"就能搜到一大堆。
  这里,我将打散这些专有名词的顺序,直接回归软件开发过程的各个相关方面。

对象的来源 - 需求
  软件是解决特定需求,完成其指定功能的程序,所以设计中对象的来源主要是围绕需求展开的。
  需求通常分为功能性需求和非功能性需求。
  功能性需求,也是硬性需求,其实就是客户需要的功能。围绕客户的业务流程,我们可以得到支撑系统功能的核心对象。比如3D软件中常用的摄像机,材质,立方体,光线,场景等,这些都是直接从业务流程中转化来的对象。
  非功能性需求,也就是软性需求,通常是指依一些条件判断系统运作情形或其特性,而不是针对系统特定行为的需求,它包括诸如安全性、可靠性、互操作性、健壮性、易使用性、可维护性、可移植性、可重用性、可扩充性等。为了达到这些条件指定的标准,我们需要提炼很多支撑系统各种非业务特性的对象。比如软件中常用的接口,抽象类,设计模式中各种中介者,代理,门面等等。离开这些对象,系统可能工作很好,但是一旦面临变化,系统的可重用性,可扩充性就不是那么好了。

对象设计目标 - "高内聚+低耦合"
  毫无疑问,系统中所有的对象都尽可能是高内聚和低耦合的,这是对象设计的理想目标,在这种设计中,每个对象功能集中,访问权限合适,修改时波及范围很小,应对变化的性能最佳,扩展起来也最方便。
  在任何的系统中,功能都是通过对象之间交互来完成的,所以对象设计中,我们根本无法做到对象之间零耦合,我们只能尽最大的努力来降低或减少对象之间的耦合,而通常的做法就是把相关功能都集中到一个对象中,并设置合适的访问权限来封装实现的细节,这其实就是信息专家的内涵。
  信息专家模式建议把类的职责相关的所有成员都集中到一起封装起来。这个原则也几乎就是前面单一职责原则的另一种表述。
  仔细看看各种发行的类库和框架,至少说写的比较好的类库,其对象基本上还是能满足高内聚低耦合的特征的。
  不管采用任何方式,不管使用任何模式,我的心中时刻都会以"高内聚低耦合"的标准来验证设计出来的对象,这是我的必经步骤。

对象设计过程 - "折中+迭代+重构"
  其实从上面的描述中我们所用的词汇可以看出,"高内聚+低耦合"这个标准注定是无法量化的,这也就注定了我们设计过程中,通常无法详细比较两种设计中内聚和耦合的程度,只能依靠直觉和经验,来折中团队中的每种意见,来达成这个目标,并且随着系统的不断发展,我们的设计也需要进行重新思考,最终的设计也会不断的调整
  每种方案都必然有优点和缺点,所有模式带来灵活性和扩展性的同时,必然会带来性能和可读性的损失,这使得我在考虑问题的时候必须要相当谨慎,必须要综合各个方面的因素和影响,来最终决定采用在当时情况下比较适合的一种方案,古人说的一句话特别有道理:因地制宜
  下面是我工作中的一个例子:
  在我的工作中,我有一个StoryboardWindow(故事板)对象,里面包含了各种制作出来的动画,为了了UI上展示这些动画元素,我们需要一些辅助方法来完成UI上面对象的创建工作,于是一个辅助类StoryboardWindowUtil诞生了,它的伪代码如下:

class StoryboardWindowUtil
{
  static Button CreateButton(/*parameters*/) { return new Button(); }
  static Label CreateLabel(/*parameters*/) { return new Label(); }
}

随着业务逻辑的越来越复杂,我们需要处理UI上控件对应的SelectionSet的一些方法,于是我们把这些方法加到这里来:

SelectionSet GetStoryboardSelectionSet();
SelectionSet GetAnimationSelectionSet();
void SelectStoryboard(/*parameters*/);
void SelectAnimation(/*parameters*/);

后来,随着UI显示一些需求,我们需要处理精度和格式化一些数据,于是加入了下面一些成员:

int Duration2Pixel(/*parameters*/);
float Pixel2Duration(/*parameters*/);
String formatTime(/*parameters*/);

再接着,由于我们需要重复使用一些UI的对象,比如StoryboardWindow,于是我们在这里缓存了这些对象:

void SetStoryboardWindow(StoryboardWindow window);
StoryboardWindow GetStoryboardWindow();
// 很多类似的这种对象
//...

  这个过程在软件停止研发前根本不会停止,随着需求的变化,类似的修改会越来越多,可以预见到随着这些修改的发生,StoryboardWindowUtil最终将不堪重负。

  在我默念了几遍了"高内聚低耦合"的标准后,我决定抽个空重构一下这个类,每次类似的修改我都不希望在StoryboardWindowUtil内进行,而是每次如果确实需要,就增加一个新类来处理新的需求,这样我就能得到下列一些高内聚的对象:

UIObjectContainer - 缓存对象的作用
SelectionSetManager - 处理SelectionSet相关事宜
UnitFormatter - 处理单位相关的转换和格式化

  且不说原来的做法都是直接在类中添加方法是不是考虑了OCP(开闭原则),光说这里是否需要新的对象也完全是反复考虑的一个过程,其实也有很多人也认为:为一两个方法创建一个新类完全是浪费的!这就是度和折中的思考过程,这个过程根本就没有一个公式来计算每种设计确定的内聚性和耦合性,而且显然每个人对度的认识和理解也是不同的。

  从上面的这个过程中,我还发现:

1. 变化是必然了,没有什么是不变的,没有什么设计是永恒的,唯一的不变是变化。
2. 软件研发是一个不断迭代的过程,职责分配在迭代过程中总是不断进行。
3. 在一些对象的职责分配上,没有什么绝对的对与错。在不同的时间点上,代码完全可能与最初的设计思想是完全背离的。

  最后送上一个N年前发生的小故事:

新人:微软为什么要推出扩展方法呢?(注:C#中的扩展方法指的是,通过一定的语法,你可以扩展一个类的方法,当引用了这个新类库后,被扩展的类的所有实例都可以调用扩展的方法)
:我觉得这种做法满足面向对象的特性,虽然我们新添加了新的静态类,但并没有改变原有的类的代码,但是使用起来就好像是原来的类有了新方法一样。
一位弟兄:我觉得不是,我觉得就是图个方便的语法。如果要谈面向对象,那么我随便把这些方法塞到一个类中,我拿这个新的类调用就行了啊,所以显然不是为了面向对象考虑。
:...

  难道随便加一个对象,使用对象的语法去写代码,就是面向对象吗?你觉得呢?

时间: 2024-08-02 14:30:03

写给自己看的小设计5 - 对象设计过程之设计目标的相关文章

写给自己看的小设计3 - 对象设计通用原则之核心原则

由于对象设计的核心是类,所以下面的原则也都基本都是讨论类的设计问题,其它类型的元素都比较简单,基本上也符合大多数这里列出的原则. 前面我们分析完了对象设计的基本原则,这里我将重新温习一下对象设计的核心原则 - SOLID原则.几乎所有的设计模式都可以看到这些原则的影子. 单一职责原则(SRP):做一个专一的人 单一职责原则的全称是Single Responsibility Principle,简称是SRP.SRP原则的定义很简单: 即不能存在多于一个导致类变更的原因.简单的说就是一个类只负责一项

写给自己看的小设计4 - 对象设计通用原则之扩展原则

除了前面学习的那些核心原则,还有一些衍生的原则,掌握它们,你将更好的面向对象.不妨称它们为"扩展原则"吧. 迪米特法则:尽量不与无关的类发生关系. 迪米特法则全称Law of Demeter,简称LoD,也称为最少知识原则(Least Knowledge Principle,LKP).这个原则没什么固定的定义,大体上有这么几种说法: 1. 只与你的朋友说话 2. 不和陌生人说话 3. 对象应该只与必须交互的对象通信 通俗地讲,一个类应该对自己需要调用的类知道得最少,你调用的类的内部是如

写给自己看的小设计7 - 对象设计过程之对象交互

对象创建完了以后,就是互相协作完成系统的功能.对象的协作方式通常有如下方式: 直接引用,互通有无 这种方式最为自然,最为直接,最为简单,也是通常情况下的首选.不管是传参数,还是直接创建后直接使用对象的方法,都是属于这种情况: public class ComponentB { public void Run(ComponentA componentA) { componentA.Say(); } } 依靠中介通信 当对象之间的交互复杂起来以后,直接的通信可能耦合度就太高了,这个时候要靠辅助对象来

写给自己看的小设计6 - 对象设计过程之对象创建

对象创建是面向对象程度的最常见活动之一.对象的创建通常有两种方式:直接创建,或者是间接创建. 直接创建对象 直接创建意味着由使用对象的元素直接创建对象,然后使用对象.这种方式最常用,也是对象之间建立耦合的最常见方式,也是如非必要,优先考虑的对象创建方式.例如下面的C#代码: public class ComponentA { // 类的成员直接new出来 ComponentB m_componentB = new ComponentB(); public ComponentA() { } pub

读书笔记:《写给大家看的设计书》

读书笔记:<写给大家看的设计书> <写给大家看的设计书>这本书本来是买给孩子看的,孩子对板报.杂志.名片等设计很感 兴趣,想看点基础的设计类的书籍,就给她找了一本.书到手后,我随手翻了翻发现对于我制作PPT还是很有帮助的,对于非专业设计人员来说,掌握4条设计原 则确实可以让设计感觉到非常专业,这几条原则应用于网站的设计也是同样有效. 全书三大部分,共14章,第一部分(第1-8章)最有用,讲述四大设计原则,第二部分(第9-11章)讲字体设计,第三部分有点像附录. 第一章 约书亚树 有

写给大家看的设计书(第3版)PDF下载高清完整扫描原版

这本书出自一位世界级设计师之手.复杂的设计原理在书中凝炼为亲密性.对齐.重复和对比4 个基本原则.作者以其简洁明快的风格,将优秀设计所必须遵循的这4 个基本原则及其背后的原理通俗易懂地展现在读者面前.本书包含大量的示例,让你了解怎样才能按照自己的方式设计出美观且内容丰富的产品. 此书适用于各行各业需要从事设计工作的读者,也适用于有经验的设计人员.需要学习的朋友可以通过网盘下载pdf版 http://putpan.com/fs/8y1i5bce5n5s1h8u0/ 作者简介 Robin Willi

写给大家看的设计书——读后笔记

<写给大家看的设计书>介绍了设计的四个基本原则:亲密性.对齐.重复.对比.作为一个软件"设计师",我也来聊聊读过这本书之后,我对这四个原则的一点理解. 亲密性 亲密性原则是指:内涵相关联的内容,在结构.关系上也应保持关联.        以软件设计的角度来说,一项业务所包含的功能.一个功能所包含的代码,应该在结构.关系上保持关联.例如把这些代码放到同一个包下.用同一套规则来命名.这样,当我们需要查阅.修改这个功能,需要处理哪些代码就"一望而知"了.   

读书笔记:《写给大家看的面向对象设计》,《程序员的职业素养》,《设计模式其实很简单》

按照上次的计划 看了三本书,笔记现在才贴出来. <写给大家看的面向对象设计>: 使用接口开发的作用 规范函数命名,特别在项目人数比较多,在设计时,定了接口命名与参数. 可以把前台与后台的脱离.定义接口后,实现接口并返回模拟的数据,例如DataTable等,前台不需等后台就可以做UI与交互,改善UI与需要的数据,发现问题并不断完善接口.后台按照需求把数据库设计好了(如果是领域驱动开发,是Model创建),按照这个接口来开发功能,完成之后前台切换过来即可.前后台是并行开发. 便于单元测试的编写,其

写给大家看的编程规范

(本文参加 2014 CSDN博文大赛,谢谢.) [文章摘要] "没有规矩,不成方圆",在实际的软件开发项目中,做任何事情都不是随心所欲的,我们编写代码需要遵守项目组约定的编程规范.很遗憾,在学校的计算机课程中,重在教导学生实现一定的程序功能,对程序的编写规范很少提及,这也就导致了从学校毕业踏上工作岗位之后一段艰辛的学习过程. 本文根据自身的软件开发实践,对实际的软件开发项目中编写C语言和SQL语言程序时所需遵守的规范进行了详细的介绍,旨在让广大即将从事软件开发工作的程序员们懂得编程规