业余C/C++ 2008-9-3 19:35
用C++做设计之二(原书摘录)
[size=2] 切不要只把C++当作更高级的C来使用。[/size]
[size=2][/size]
[size=2] 以下是《C++程序设计语言》中有关设计摘录的第二部分。[/size]
[size=2]23.4.3.2 步骤2:描述操作[/size]
[size=2] [b]精化有关的类,描述它们的一组操作。[/b]很自然,不可能将找类的工作与确定它们所需操作的工作分开。然而,这里也存在着一种实际的区分,确定类时的注目点在于关键性的概念,有意淡化了类的计算性质;而在描述操作时,关心的就是找出一组完整可用的操作。同时考虑这两方面的问题常常会因为太困难而没法做,特别是因为许多相关的类还需要一起设计。在需要同时考虑这些问题的时候,CRC卡片[color=red]①[/color]常常很有帮助(23.4.3.1节)。[/size]
[size=2] 在考虑需要提供哪些函数时,存在几种不同的哲学。我建议采用如下策略:[/size]
[size=2] 『1』考虑这个类的对象应该如何构造、复制(如果允许)和析构。[/size]
[size=2] 『2』定义由该类所代表的概念所需要的最小操作组合。典型情况是将它们作为成员函数(10.3节)。[/size]
[size=2] 『3』考虑为了记述的方便需要增加哪些操作,只将其中最重要的几个包括进来。这些操作常常称为非成员的“协助函数”(10.3.2节)。[/size]
[size=2] 『4』考虑哪些操作应该是虚的,也就是确定那些将这个类作为界面,应该有派生提供实现的函数。[/size]
[size=2] 『5』对于本组件中所有的类,通盘考虑它们在命名方面和功能方面可能取得那些共性。[/size]
[size=2]这很显然是一种最小化的说法。把想像中可能有用的函数都加进去,并把所有函数都做成虚的将容易许多。但是,函数越多,其中的某些函数始终都不使用的可能性也就越大,它们也更容易限制随后的实现以及系统将来的演化。特别是那些直接读写对象状态中某些部分的函数,它们常常会将类束缚于某种单一的实现策略,从而严重地限制重新设计的可能性。这种函数降低了抽象的层次,使之脱离了被实现的概念。增加函数也增加了实现者的工作——还有重新设计时的设计师的工作[color=red](作者想得很远啊,值得我们学习,我想这也是一条好的工作经验了)[/color][color=black]。于某个函数已编程一种责任之后再去删除它相比,在某种需要已经弄清楚后加入一个函数,做起来容易得多。[/color][/size]
[size=2] 这里要求明确地做出将函数作为虚函数的决策,而没有把这件事情作为默认规定或者实现细节。提出这种要求的原因是,将一个函数做成虚函数,将其所在类的使用以及这个类与其他类的关系[color=red](之间)[/color]产生至关重要的影响。即使在某个类里只有一个虚函数,该类的对象的布局也不会像C或者Fortran语言的对象那样简单了。还有,如果某个类有了一个虚函数,它也就有潜力为一些尚未实现的类的界面,而这个虚函数也就隐含着对那些尚未实现的类的依赖性(24.3.2.1节)。[/size]
[size=2] 注意,这种最小化要求设计师做更多的工作,而不是更少。[/size]
[size=2] 在选择函数时,最重要的就是几种关心它应该做什么,而不是去关心它应该怎样做。也就是说,我们应该更多的注意所需要的行为,而不是去关心实现中的问题。[/size]
[size=2] 按照函数对于对象内部状态的使用情况将它们分类,有时候也很有用处:[/size]
[size=2] ——基础操作:构造函数、析构函数和复制操作。[/size]
[size=2] ——探查操作:那些不改变对象状态的操作。[/size]
[size=2] ——修改操作:修改对象内部状态的操作。[/size]
[size=2] ——转换操作:那些基于操作所针对的对象的值(状态),产生其他类型的对象的操作。[/size]
[size=2] ——迭代器:访问或使用所有包容的一系列对象的操作。[/size]
[size=2]这些分类并不是不相关的。例如,一个迭代器也可以设计为一个探查操作或者修改操作。这些类比也就是一种分类方式,可以帮助人们处理类界面的设计问题。很自然,同样可以有其他的分类方式。这种分类方式在维持一个组件内部各个类间的统一性方面特别有用。[/size]
[size=2] C++通过[b]const[/b]和非[b]const[/b]成员函数的方式支持对探查操作和修改操作的划分。与此类似,这里还直接支持构造函数、析构函数、复制操作和转换函数的概念。[/size]
[size=2]23.4.3.3 步骤3:描述依赖性[/size]
[size=2] [b]精化有关的类,描述它们之间的依赖关系。[/b]24.3节里讨论了各种各样的依赖关系。在设计环节中,特别应该考虑的是参数化、继承关系和使用关系。这里的每种关系都涉及到有关一个类在为系统某单一性质负责的意义方面的考虑。担负起明确的责任并不意味着这个类本身就要保存所有的数据,也不意味着它的成员函数需要直接完成所有必须的操作。与此相反,每个只需占有责任中的一片领地的类,应保证这个类完成的大部分工作是通过直接请求“其他地方”,由另一些以有关的子工作为己任的类去处理。当然也要小心,这种技术的过度使用可能导致低效率和无法理解的设计,可能做出大量的类和对象,以至于它们什么都不做,只去产生瀑布式向前传送的服务请求。如果某件事现在[b]可以[/b]在这里做,就应该在这里完成。[/size]
[size=2] 在设计阶段(而不是实现阶段)就需要考虑继承关系和使用关系,这一需求直接源于采用类来表示概念。它也意味着设计的单位应该是组件(23.4.3、24.4节),而不是类。[/size]
[size=2] 参数化(常常引向使用模板)是一种将隐式的依赖关系显示化的方法,这样可以同时表述几种选择,而不必增加新概念。经常有这样的选择:是把某种东西留作对环境的依赖关系,将它表述为继承树上的一个分支,还是采用一个参数(24.4.1节)。[/size]
[size=2]23.4.3.4 步骤4:描述界面[/size]
[size=2] [b]描述界面。[/b]私有函数通常不必在设计阶段考虑。在设计阶段必须考虑的实现问题最好是作为关于依赖性的步骤2中的一部分进行处理。说得更强一些[color=red](汗啊)[/color][color=black],我使用一条经验规则:对于一个类而言,除非它存在着至少两种有显著差异的实现方式,否则这里大概就有些问题。也就是说,这可能是一个伪装成类的具体实现,而不是某个真实概念的代表。在许多情况下,考虑对于一个类是否可能做某种形式的延迟求值,是回答下面问题的一种很好方式:“这个类的界面是与实现无关的吗?”[/color][/size]
[size=2] 请注意,公用基类和友元也是一个类的界面的一部分,参见11.5节和24.4.2节。通过分别定义保护界面和公用界面,为继承和普通客户提供分离的界面,这种工作会有很好的回报。[/size]
[size=2] 正是在这个步骤中,应该考虑和描述参数的确切类型。这里的理想[color=red](结果)[/color]应该是使尽可能多的界面能利用应用层的类型静态确定。参见24.2.3节和24.4.2节。[/size]
[size=2] 在描述界面时,也应该为这些类向外看一看,是不是某些操作支持了多于一个的抽象层次。举个例子,类[b]File[/b]的某些成员函数可能有类型为[b]File_descriptor[/b]类的参数,它的另一些函数可能以表示文件名的字符串为参数。[b]File_descriptor[/b]的操作与文件名操作并不在同一个抽象层次里,所以就应该怀疑它们是否应该在同一个类中。或许最好是两个文件类,一个支持文件描述符的概念,另一个支持文件名的概念。在典型情况下,一个类里的所有操作都应该支持同一个抽象层次。如果它们不是这样的,那么就应该考虑重新组织这个类及其相关的类。[/size]
[size=2][/size]
[size=2](原文摘录到此结束)[/size]
[size=2]一下就对文中的①,即CRC卡片作下介绍,因为觉得运用这种卡片的过程对我们分析程序中的类和理解MFC框架有点帮助。一下一段内容摘自《21天学通C++》,作者Jesse Liberty,人民邮电出版社。当时买这书,完全是希望借助它入个门。[/size]
[size=2] [b]CRC卡片[/b][/size]
[size=2] CRC一词表示类(class)、责任(Responsibility)和合作(Collaboration)。一张CRC卡片是一张4×6的索引卡片。这种简单的技术含量低的设备使你能够和其他人员一起工作来理解你的初始系列类的主要责任。[/size]
[size=2] 。。。[/size]
[size=2] CRC卡片的关键特征是使它们拟人化——就是说,给每个类赋予像人一样的品性。这里是其工作方式:在你有了一组初步的类后,返回到你的CRC方案中去。在桌上任意地分开这些卡片,并与方案一起分析。例如,让我们返回到下面的情况:[/size]
[size=2] 顾客从活期账户中提取现金。账户中有足够的现金,ATM[color=red](自动取款机)[/color]中也有足够的现金及收据,且网络是打开并运行的。[/size]
[size=2] ATM请顾客给出要提款的数额,顾客请求提取300美元,这时是一个合法提款数量。机器给出300美元并打印出一张收据,而顾客取出钱及收据。[color=red](生活中处处都能体现出程序设计,呵呵)[/color][/size]
[size=2][color=#ff0000] [color=black]假设我们在CRC会议中有五个与会者:Amy是助手和面向对象的设计员;Barry是首席程序员;Charlie是顾客;Dorris是领域专家;而Ed是一个程序员。[/color][color=red](一个工作团队的缩影,不是每本书里都能这么大胆的说出这些)[/color][/color][/size]
[size=2][color=#ff0000] [color=black]Amy拿起一张表示CheckingAccount[color=red](检查账户)[/color]的CRC卡片并说“我[b]告诉顾客有多少钱[/b]。而顾客让我[b]给他300美元[/b]。我[b]发一条消息给出纳员[/b][color=red](消息映射的影子出来了)[/color]告诉他给出300元现金。”Barry拿起他的卡片说“我是出纳;我[b]分300元[/b]并[b]给Amy发一条消息[/b],告诉她[b]从她的存款中减去300元[/b]。那个机器现在[b]少了300美元[/b],我告诉谁呢?我要记录这一情况吗?”Charlie说“我认为我们[b]需要一个对象来记录机器中的现金[/b]”。Ed说“不,出纳应该知道机器有多少现金;那是[b]出纳工作的一部分[/b]”。Amy不同意:“不,必须有人协调现金的支出。出纳[b]需要知道[/b]是否有现金及客户是否在账户中[b]有足够的存款[/b],而且要[b]数出钱[/b]并知道[b]何时关上抽屉[/b]。它应该负责[b]记录手上的现金[/b]——某种内部账户。无论谁手上的现金也可以告诉内部办公系统何时该[b]重新装添[/b]。不然,那就会[b]让出纳做太多的事情[/b]。”[/color][/color][/size]
[size=2] 讨论继续进行。通过举起卡片并互相交流,处理了所委托的需求和机会;每个类都变活了,并且它的责任是很明确的。当小组在设计问题中陷入困境后,助手可以做出一个决定,帮助小组进行讨论。[/size]
[size=2][/size]
[size=2](摘录完毕)[/size]
[size=2] [/size]
[size=2] 最后,要跟大家说:“如果你是沙地上的珍珠,就一定会有人捡起你,否则,则说明你其实只是一粒沙子。”但愿所有真正有技术、有创新的人,能摆脱社会的不公平的束缚,得到自己理想的工作。[/size]
[size=2] 诚挚希望以上内容对大家学习C++能有所帮助。[/size]
[size=2][/size]
[size=2] 业余C/C++拜上 [/size]
[size=2][/size]
[[i] 本帖最后由 业余C/C++ 于 2008-9-3 19:37 编辑 [/i]]