C++编程惯用法----第1章 抽象(其他章节不咋样,不转载了)

1章抽象

    数据抽象(dataabstraction)是面向对象设计的一个重要概念。数据抽象要优先于面向对象的设计;然而,随着C++这样直接支持数据抽象的语言变得流行起来,它的应用范围也变得越来越广泛。

    抽象数据类型(abstract data type,也称为ADT)是一种由用户定义、拥有明显不同的两部分的类型:

    一个公用的接口(public interface),用于指定用户使用该类型的方式,而一个私用的实现(private implementation),在类型内部被使用,以提供公用接口所给定的功能。

    C++中,privatepublic关键字被用于指定类声明中哪部分是实现,哪部分又是接口。通过这种方法,编译器就可以确保类的使用者不会绕过类的接口而直接访问其私用成员。然而,另一个事实就是,类有私用成员也不表示它的设计就很好。

    对干privatepublic来说,它们本身还存在着一个远比低层次语言规则更重要的思想。这个思想还从末被C++编译器显式地检测过,但它对于设计模块化、易维护的程序来说却是至关重要。它表述的是一种概念,那就是——每个编程问题的解决方案都可以被划分为两部分:一个抽象模型(abstract model),它是在程序员和程序用户之间都能取得一致意见的、用于描述该问题以及其解决方案的智力模型;对于该模型的实现(implementation),就是程序员使用特定的方法使计算机可以表述出这种抽象。在本章中,我们将会通过对一个类的设计、细化以及记录过程,来逐步体现出抽象的过程。如我们将看到的那样,“简单地书写一页手册,然后让代码来适合它”这种方式在程序设计中是行不通的

    下面是一些理由,用于说明为什么一个经过详细考虑并记录下来的抽象模型对于程序设计来说很重要:

    它有助于帮助其他人来理解如何使用你所设计的类。如果你正试图使用一个链表类,你的主要的(也是最初的)考虑应该不是包含这个类的头文件的名字,也不是该类中成员函数的名字以及其参数类型等。相对来说,你会更注重那些有关该类抽象模型的基础问题,如:它提供了什么操作?能不能在链表上进行回退?能不能在常数时间内访问到链表的头和尾?同一对象能不能在一个链表中出现多次?链表中包含的是对象本身呢,还是对对象的引用?如果链表包含的是对象的引用,那么由谁来负责创建及销毁这些对象?当链表中的某个对象被销毁时,链表应该有什么样的调整?等等。

    对这些问题的冋答将会直接影响到那些使用该链表类的应用程序的设计。(例如,如果你的应用程序需要能够在链表上双向移动,那么一个单向链表类在此处就不再适合了。)

    如果你不能理解并记录下你的类所支持的抽象模型,那么用户就可能会选择不使用你所提供的类(更糟的是,用户决定使用你的类,然后却发现他们做了一个错误的选择)。

    抽象模型是你与你的用户之间的一个协议。由于抽象模型对用户的程序设计有着很重要的影响,你将发现对它进行不向上兼容的改动会有多困难,有时那样的改动甚至就是不可能做到的。例如,一个去除掉链表中后退功能的决议,对公开使用的List类来说将会是一场灾难。为什么这样说呢?因为某些List的用户可能会将其应用程序的设计完全围绕着它能够支持后退这种抽象来进行。这种在抽象模型中的不兼容改动肯定会使某些己有的用户陷入困境。如果一个类己经被广泛使用,那么它的每一个特性都将至少被一个用户所使用,如果你轻率地决定去除掉某个特性,必将在特定的用户群中引起骚乱

    这就意味着,在你决定将你的库发布给客户时,对库的柚象模型进行细化和完善尤为重要。在实现中产生的错误通常都可以很容易地在下一个版本中得到更正;但在抽象模型中产生的问题(除去冗余错误之外)则将持续存在于类的整个生命周期中。

    在记录抽象模型的过程中,我们常常可以发现设计中的重要缺陷在软件项目设计的早期,我们通常会过高地估计我们对于问题以及我们所提出的解决方案的理解程度。在将早期的模糊想法用精确的言语表示清楚的过程(即抽象模型的记录过程)中,我们就能更关注于那些我们以前从未考虑到的方面。毕竟,在认识不清晰的时候,我们是无法提出合理的解决方案的。

    清晰的抽象模型文档有助于其他人重新构造出你的类的新版本(这包括继承或是重新实现两种方式)。要想实现出一个能够与现有代码共同工作的新版本的类,你所需做的不止是在成员函数的名字以及类型签名上做到和最初的代码完全匹配;更重要的是,新版本的类必须还能够符合旧版本的抽象模型的要求

    一旦理解了抽象模型,我们就可以避免“以实现来驱动设计”的情况出现不管其承认与否,许多的软件设计者在设计一个新类的接口时,他们的脑海中都已经有了一个“明显”的实现方案。这就会使得他们在设计中不自觉地将抽象模型向这种实现方式靠拢。这种做法不但不能提供一个用户易于理解的、并且认为就应该如此的接口,它还会使实现的细节遍布于接口之中,导致以后对实现进行改动变得异常困难

    当然,其他极端的做法也同样会导致麻烦的出现(如:设计出一个完全不顾及实现方案的可能性的接口)。一个接口,不管它有多么优雅,如果它不可能被实现出来,或是实现它需要一些让人无法接受的性能上的损失,那么对用户来说它就起不到任何帮助作用。在这些考量之间取得一个合理的平衡是类设计中最具有挑战性的部分。最后说明一点,设计抽象模型和设计实现方案应该是两个独立的行为。但尽管如此,这并不意味着我们需要用不同的人手来分开处理这两部分,重要的是,开发人员必须知道自己在某个特定的时间时,自己到底是在负责抽象呢,还是负责实现。

    仅仅考虑抽象(而不是实现),我们有几种常用的方法。在抽象的过程中决定“什么应该有,什么不应该有”是面向对象设计者的一个关键技巧。在下一小节中,我们将开始构造出—个简单的抽象模型。

    1.1 有关电话号码的抽象模型

    在本节中,我们将对用于表示电话号码的类的抽象模型进行最初的探讨。我们期望用这个类来代指各种电话应用程序(如交换机系统、账单系统等等)中的电话号码。

    我们应该怎么开始我们的第一步呢?一个不错的方法就是:用一句话来描述该对象是用来干什么的(注意,不是“是什么”)。这种描述方式应该尽可能的抽象,尽量不要涉及到对象的内部结沟。这种“官面总结(executive summary)”成该尽可能的简单,简单到即便是行政人员也可以轻而易举地理解它。

    例如,下面就是对于电话号码的一个糟糕的官面总结:

    “电话号码包括3位地区号码,后面紧接着3位交换机号码,最后是4位数字。”

    这样的描述并没有提及电话号码是用来做什么,或者它和谁交互的问题。相反的是,它简单地把电话号码的结构描述了一遍。这不但没有解释清楚电话号码是做什么的,它还限制了电话号码的范围。例如,通过modem所拨打的电话号码可以含有非数字的字符(如&就用于告诉modem来等待第二次的拨号音,另外还有#*这两个特殊的按键)。更严重的就是,它还限制了在美国和加拿大之间能够合法通话的电话号码集。

    下面的修改就要稍微好一些:

    一个电话号码指定了世界上的某一部特定电话

    此处我们并没有丝毫提及电话号码的结构,它只是阐述了另一个用于电话号码的抽象。当你写下这样的句子后,请花一些时间来仔细琢磨它。请像律师对待一份合法的合同那样仔细检査其中的每个单词。这句话中有什么明显的漏洞吗?它所隐含表达的意思又是什么?

    让我们先来注意“指定”这个词。它意味着什么呢?一个电话号码是不是惟一指定一部电话呢?换句话说,能不能有两部以上的电话共用同一个号码呢?事实上,在许多的商业机构中,大部分电话都是共用一个电话号码的:在有电话进来时,会有一个本地的交换机将它转到一部空闲的电话上面去。同样,对于一个特定的电话来说,电话号码也不是惟一的。当你拨打555-1234时,对方的电话就取决于你拨打时所处的区域

    这样,我们就得知了:一个电话号码并不代表惟一一部电话,它的含义取决于使用它的区域。为了反映出这种更新,我们进行了如下的官面总结:

    “当与呼叫电话关连起来时,电话号码就是决定被呼电话的关键。

    这样,我们就把电话号码与呼出电话的关系给涵盖进去了,并且也隐式地表达了被呼电话的惟一性。但让我们仔细看看这句话的后半部分。是不是说你拨打电话后就可以得到被呼电话呢?也许如此吧,但更精确的说法是:拨打电话会导致一个到被呼电话的连接的建立。这两种说法之间有什么不同呃?它们之间的不同完全取决于电话用户是否会对连接本身有兴趣。那么,在两台电话间的连接有什么属性会让人们感兴趣呢?当然有了:一个连接有一个开始时间(starttime,―个结束时间(endtime,以及一定的开销。它们不属于这两台电话,而仅仅是电话连接本身的属性。

    拨号必定产生一个被呼电话的结论并不总是正确的:有时线路可能比较繁忙;有时也可能因为某些其他原因会导致呼叫失败……所有这些都不会产生我们所期望的被呼电话。有鉴于此,我们将上面的抽象结论继续细化为;

    “当从呼叫电话拨号后,电话号码就是决定可能连接到的被呼电话的关键。

    上面的句子与我们开始时所给出的抽象表述相比,它更贴近实际应用中的抽象模型。然而,要想真正地了解它的含义,我们还必须定义一些它所依赖的其他抽象模型。例如:

    “(电话间的)连接表示的是在两部(或多部)电话间的逻辑连接。

    1.1.1 什么不应该出现在抽象模型中?

    没有出现在抽象模型中的东西和存在于模型中的一样重要。在我们上面给出的那个电话号码的抽象模型中,我们并没有假设出:一个电话号码有多少位;它是否包含非数字字符;电话与电话号码之间是否是一一对应的以及它们之间的连接是如何建立的。

    在保持实用性的前提下,我们通过尽可能地将该模型最小化以使其尽可能通用化。我们提高了它应付今后未知变化的能力,以避免频繁地修改我们的抽象模型(但实现细节可能有所改变)。例如:蜂窝电话的出现或者是标准电话系统中新增的按键都不致迫使我们修改我们的抽象模型。

    1.1.2 如果存在疑问,暂不考虑

    在吃不准某个特殊的概念是否应该包含于抽象模型中时,通常的安全做法是不去考虑它,而只是把它包含入我们的抽象模型中去。这是因为,由于缺少造成的错误通常都可以用一种向上兼容的方法进行修复——一般来说我们只需在类中新增一个成员函数就可以了。扩展接口总是比缩减接口要容易得多。从另一方面来说,从类中去除掉一个成员函数通常很难做到与现有代码兼容,因为可能己经有的用户代码中就调用了这个被去除的函数。

    1.2 抽象模型间的关系

    作为初始设计过程的部分,设计人员应该仔细地考虑抽象模型与其他模型间的交互,问自己一系列相关的问题。这并不意味着我们需要一个正式的问题清单,根据应用程序的不同,可能还存在着其他的一些同样重要的问题需要设计人员去早早检测。在本小节中,我们只给出那些经常出现的问题。

    问自己这些问题还有着另外一个重要的好处。设计者经常犯的错误就是:这些问题中的部分通常都具有一个看起来十分明显的答案,不过事实上这个答案却是错误的。当你在设计时问自己这些问题时,最好在那些能够很容易就得到答案的问题上停下来,并更加深入地对它们进行思索。这个(显而易见的)答案是不是对的呢?试图去设想一个可以证明它是错误的场景。只要你能够在这种思索过程中找到哪怕只是一个错误场景,你都有可能会因此避免一个将来会给你带来高昂代价的设计错误

    1.2.1 ―对一,一对多,多对一,还是多对多?

    现在我们来考虑电话和连接之间的关系。一个电话是否可以有多个连接呢?通常的答案都是肯定的,通话中的一方可以接通新的电话而不挂断已有连接,我们同样也要考虑相反的另外一个问题:是否可以有多于一部的电话参与到一个连接中呢?在此处的答案毫无疑问也是肯定的,因为这就是电话的核心用途!当我们关注于这个话题时,我们应该考虑的是,是否可以有多于两部的电话同时参与到一个连接中呢?虽然可能有点会令人感到惊讶,但这个问题的答案同样也是肯定的,电话会议在同一时间的参与者可以多于二个。因此我们得到的结论是:电话和连接之间的关系是一种多对多的关系。

    作为另外一个例子,我们来考虑电话号码与账单地址之间的关系。一个电话号码只能属于―个客户,由此它也只有一个相关的账单地址。然而,同样一个地址却可以有多个电话号码与之相关联。这就是一种多对一的关系(多个电话号码对应于一个账单地址)。

    1.2.2 多少才算“多”?

    如果连接是多对一或者多对多,那么让我们来想想:究竟这个“多”暗示着多少个对象呢?此时,我们并不需要一个精确的数字,我们想知道的只是,“多”究竟是代表着以下哪一种:

    •二(或者大于它的一个常数数字);

    •某个常量范围;例如,如果我们讨论的抽象是“一周之内的天数”,那么在一个这样的抽象中,我们最多只能有7个这样的对象;

    •某个变化的范围,大概有好几(百?千?万?)。

    需要小心的是:即便是定义得很好的抽象模型,它最终还是会被别人以一种设计者从来都没有考虑过的方式使用。如果我们假设“多数”永远不会超过几十,并由此在实现中使用了一个二次方程式的算法,那么我们的用户在上千个对象的场景下使用我们的实现时,就将得到令人惊讶兼恶心的结果。

    1.2.3 如何来保证关系会随着时间的改变而改变?

    我们是否必须在对象被创建时确立下来关系的存在呢?当关系被确立后,新创建的对象是否可以参与到其中呢?

    显然,电话号码可以独立于连接而存在;但连接无论在何时都必须至少涉及到一个电话号码。对于连接的另一端的电话,我们对于它有什么要求?是否应该在连接建立的同时就提供它,还是在连接建立后再把它加入连接?从另一方面来考虑的是,一个连接是否可以只涉及一个电话存在?检索(而不是呼叫)电话号码的行为是否会建立一个连接?

    回答这些问题需要我们对使用这些对象的应用有着相当的了解。我们需要知道足够多的电话学知识,以此来决定应用程序是否需要在连接正在建立或者中断时对它进行访问。为了讨论方便,我们假定这样的连接有着实际上的作用;例如:计费软件可能需要访问它来记录每次呼叫结束后应该花费的费用。这意味着被呼叫方应该在连接建立时就加入到连接中去。

    这个模型同样也可以很好地应用到三方通话中去。此时的连接并不是在同时被建立的,而是随着时间的过去才被建立起来。每次当一个新的成员被加入到通话中时,他们只需要加入已有的连接就可以了。

    此时,我们已经对前面的“官面总结”进行了一个细化。那个总结暗示着:一个连接只能存在于两部(或多部)的电话之间。现在我们认识到,那种说法并不全对:我们对于那些可以通过电话网络在一组电话(一部或多部)间相互交谈的方式更感兴趣。于是,我们又可以将总结改为如下形式:

    “连接表示的是一组(一部或多部)电话间通过电话网络相互进行交流。

    基于我们对它的不断深入了解,得到这样的抽象细化是一件很正常的事情。在此例中,“连接”这个名词仍然有其意义;但它并不总是对的,我们应该随时准备,在原有的名字不能充分地阐述其意义时为抽象另取一个合适的名字。

    1.2.4 Is-AHas-A,还是Use-A

    不同的抽象以不同的方式相关联。我们可以把三种最常见的关系归纳为:is-ahas-a以及use-a

    is-a关系

    当两个类之间存在着下述关系时,我们就说它们之间存在着is-a关系:其中一个类所描述的对象属于另外一个类所描述的对象集。例如:Stuenbaker是一种(is-aCar

    is-a关系的另外一种说法就是子类型化。子类型(subtype)是某些更为通用的类型[也称为超类型(supertype]的特殊化;子类型的对象同样也是超类型的对象。这些术语源自于Smalltalk,许多C++程序员则使用C++中具有相同意义的两个词:派生类(derived class)来代替子类型,基类(base class)来代替父类型。

    例如:“按键电话”和"电话”之间的关系是什么?每部按键电话同样也是一部电话。按键电话只是电话中的一个子集。

    C++中,我们使用公用继承来表示is-a关系;类Push__button_phone应该派生自类Telephone

    classTelephone {

        //Telephone stuff...

    };

    class Push_button_phone : public Telephone {

        //push_button_phone stuff...

    };

    子类型(或者说派生类)可以对父类型(或者说基类)进行扩展;一部按键电话可以做一些其他电话做不到的事情。但子类型永远也不能对父类型有所限制(例如:在派生类将一个在基类中的公用成员函数改为私用);同样,电话能够做到的事情,按键电话都应该能做到。(如果不是这样的话,按键电话也就不应该是电话中的一个子集。)

    has-a关系

    has-a关系意味着包含,如果在概念上事物A包含事物B,那么A也就拥有B。电话包括一个扬声器和一个麦克风;按键电话还包括一个键盘。

    is-a不同的是,在has-a关系中,没有一个对象是另外一个对象的特例,取而代之的是,一个对象是另外一个对象的一部分。一个Push_button_phone并不是一个Keypad,而是拥有一个Keypad

    C++中的has-a关系的通常实现方式是将被包含的对象作为包含它的对象的一个成员。然而,实际上我们并不一定需要这样做。虽然在概念上来讲,Keypad被包含于Push_button_phone中,但这并不意味着我们在实现这种关系时必须使用真正的包含。在实际中,总有很多好的理由让我们不必那么做(我们将在第3章讲述这一点):数据抽象的一个好处在于,对象的实现方式并不需要完全与抽象结构相匹配。我们不应该把这种方式(指用成员来实现has-a关系)来作为主要的考量标准,因为我们所处理的是抽象之间的关系,而不是它们的实现细节。

    use-a关系

    use-a关系应用得最普遍:在这种关系中,没有一个时象会是另外一种对象(is-a),也没有一个对象包含另外一个对象(has-a)。取而代之的是,这两个对象仅在程序中某些点进行简单的联系。在我们的电话例子中,电话和连接间具有的就是use-a关系。

    相互使用的对象间通常通过调用彼此的成员函数来进行联系。当然,还存在着其他的联系方式(如:共享内存。或者是某种消息传输机制)可以做到这一点。

    1.2.5 关系是单向的还是双向的?

    给定两个对象之间的一个关系,我们是否就可以通过其中的一个对象来得到另外一个对象的信息呢?也就是说,如果我们已经知道了电话和连接之间的关系,那么我们是否可以从连接中得到有关电话的信息,或者是从电话中得到连接的信息呢?这个问题的答案对于编写实现该抽象模型的程序的性能来说,起着本质的影响作用。

    单向关系可以用C++中的指针来实现。给定一个对象,你就可以通过该指针得到另外一个对象。这种行为的反转(从一个对象得到指向它的对象)则非常困难;因为它通常都涉及到从一大堆对象中进行检索,判断一个对象是否正好指向给定的那个对象。根据具体情况的变化,这种做法有时也并不完全那么不好,导致的开销也不那么大,或者是完成的困难程度并不是不可能达到的。

    双向关系的使用要简单得多了:给定任意一个对象,我们都可以直接地找到另外的那个对象。然而,建立一个双向关系的开销也要比建立单向关系的开销大得多。我们需要对这两个对象同时进行一定程度的修改,双向关系可能会导致要求更多的空间,更多的运行时间;这一点在关系随着时间同时变化时更为明显。改变一个单向关系可能只涉及到一个指针的改变。由于往两个对象中增添新的关系可能会导致已有关系的失衡,改变一个双向关系的操作可能涉及到三个(甚至更多的)指针变化。在两个对象间,单向关系要显得更快、更小,也更容易被编程实现,但它却限制了用户使用抽象模型的能力。这也是实现细节可以(也是必要)影响设计思路的一个常见的地方。

    再回到我们关于电话的那个例子中来,电话和连接之间的关系在大多数情况下都应该是双向的:只要给定一部电话,我们就可以找到连接端的另外一部电话。如果经验不足,我们则可能会采取另外一种方法,那就是:查找每一部己有的电话,判断它们是不是处在同一个连接中。

    通常我们的选择都不是那么简单明了。例如,考虑一个编译器,在其中表达式被作为一个树状的结构来处理,我们可以将a=b+c表示成图1.2中所示的树。那么在树中,节点和它们的子节点之间的关系是单向的还是双向的呢?在实际中,为了提髙效率,这个关系通常都是单向的,程序也可以以此来编写。例如,如果我们都是自顶而下地来处理每棵树,那么在树中保留一个指向父节点的指针则显得有点多余。

    =

a        +

       b    c

    1.2表达式树

    1.2.6 两个对象中是否可以有着多重同样的关系?

    给定两个对象间的关系,我们是否可以在这两个对象中保留多份该关系呢?例如:“父亲”这个关系只能在两个对象间存在一份,一个对象只能是或者不是另外那个对象的父亲。诸如“他是我父亲父亲”这样的表述显得毫无意义。而对于关系“文章引用的书籍”來说,当文章中引用同一本书中的内容超过两次后则可以存在多份这样的关系。

    1.2.7 关系是否是必需的?

    对于每个对象来说,关系中是否就一定要至少包含一个对象呢?例如,如果有一个用来表示职工的类Employee,那么关系“向……汇报”是否就一定要包括所有的职工呢?对于部门的主管来说,它应该如何处理?我们的类的结构是否支持主管向整个部门进行汇报的功能?把主管的汇报对象改为空(或者是他自己)会不会更容易一些?

    1.3 请考虑边界条件

    你可能会对我们为什么需要考虑部门主管这种情况感到惊讶。毕竞,大部分的职工还不是部门主管。现在在设计中考虑这种特殊的情况会不会显得有点早呢?

    边界条件存在的地方总是不能满足抽象模型的假设基础。它们可以帮助我们更加关注这些假设条件。如:假设条件是否有效?在何种情况下,假设会失败?是否存在着一种合理的解决办法来处理这些情况?举例来说,如果我们假设每个职工都向另外一个职工汇报工作,那么什么时候这种假设会失效?在这种情况下,它的结果又将如何?

   边界条件通常都会导致程序bug的产生,而且这些bug也不是很容易被及时发现。与其在事发后再来寻找原因,还不如早早地把边界条件列入我们的考虑范围。早早考虑并不意味着我们需要在设计阶段就把每个边界条件的行为都固定下来。随着设计的进行,对问题的理解加深,我扪可能会对如何处理部门主管的行为进行修改。但是,为了避免在系统测试或者发布后再来解决这个问题,我们最好还是在前期设计就将边界条件纳入我们的考虑范围。

    1.4 使用CRC卡片来辅助设计

    Kent BeckWard Cunningham设计的CRC卡片是一种用来帮助设计人员在设计阶段前期进行设计的工具,此时设计人员应该做的事情就是:从问题域中找到合适的对象并理解它们之间的关系

    CRC卡片的含义十分简单,也没有什么技术含量在里面。当一群人聚在一起开始进行高层设计时,他们会为每个潜在的类建立一张索引卡片,在该卡片上面,他们会写上类的名字,它主要的功能,以及它将会和哪些类进行交互

    有了这些卡片后,设计者就可以利用它们来模拟不同的场景。每个参与者负责其中一个或多个对象的角色,然后描述他的对象在面对其他对象的请求时应该做出的反应。通过这种角色模拟,设计者就可以进一步细化系统中类的职能和协作,并把它们记录在卡片上。整个设计小组可以也基于这种讨论来为那些有着不精确(或者是模糊)含义名字的类取一个更贴切的名字

    由于卡片上面的空间有限,这就鼓励设计者们尽可能采取简单的抽象模型——我们很难在―张索引卡片上面描述出复杂的抽象模型。这种卡片也有助于设计者将类进行分类,以构造合适的继承体系。我们可以将那些获得大家一致通过的卡片放到桌于上面,并接着对那些剩下的卡片进行重复的讨论,直到大家取得一致意见为止

    进行这样的角色模拟有时可能会出现一些偏离项目的话题。但它同时也鼓励大家进行“头脑风暴(brainstorm)”将自己认为想的比较好的东西在非正式的场合中提出来和大家一起讨论。虽然大部分的离题讨论都将无疾而终,但有时偶尔的一两个火花也会产生那些在传统且正式的设计复审时产生不了的突破

    CRC卡片并不能代替完备的、用来描述类的行为细节的文档。它们主要的作用也就存在于设计的初始阶段:它们可以帮助设计人员正确地得到高层框架的信息,更早(而不是更晚)地理解和分辨出那些重要的问题

    注意:它们不可能像索引卡片那样被整理,你可以将它们粘在墙上,并保留对它们进行重新排列的权利(就像你使用Post-It随手贴一样)。

    1.5 小结

    •设计抽象模型和设计实现细节应该是两个独立且相关的行为。

    •没有出现在抽象模型中的东西和出现在其中的东西一样重要。

    •如杲存在疑问,先不要去考虑它。添加新的功能通常都不会有兼容性的问题,但是去除一个已有的功能则可能会导致代码失效。

    •彻底地检测和记录下设计中的关键点。

    •设计是一个迭代的过程。

    •请考虑边界条件。

    •在设计的初始阶段使用CRC_片。

    1.6 问题

    1.我们的电话号码的抽象模型是否还需添加其他新的抽象模型来更好地完善它?

    2.如果拨号由于某种原因(例如线路忙)失败了,那么应该有什么发生呢?我们的抽象模型在这种情况下的反应应该是怎么样的呢?

    3.我们的Connection类假设至少有一部电话被加入到连接中去。现在我们考虑对它进行一些修改以适应下面这种情况:在创建Connection对象时,我们并没有给出任何的电话对象。在这种情况下,呼叫是如何产生的?这样做是使得我们的抽象模型更简单了呢?还是更复杂了?

    4.从下面的分组中找出具有is-a关系的对来:

    ♦生物学家

    ♦罪犯

    ♦人

    ♦科学家

    ♦活体解剖论者

    5.ChildFather之间的关系是一对一,一对多,还是多对多?对于这个问题的回答,你主要应该阐明的条件和假设是什么?

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
作者:(美)默里 著,王昕 译 出版日期:2004-2-1 出版社:其它 页数:208 ISBN:9787508319124 文件格式:PDF 书籍简介 本书第1版荣获美国“软件开发”杂志评选的1996年图书震撼大奖(Jolt Award),中文版自2000年推出以来,经久不衰,获得了读者的充分肯定和高度评价。 第2版与第1版相比,在章节安排上有以下改变。增加了两:“对象的创建与使用”和“C++中的C”。前者与“对象导言”实际上是第1版“对象的演化”一 的彻底重写,增加了近几年面向对象方编程的最新研究与实践的丰硕成果;后者的添加使不熟悉C的读者可以直接使用本书。删去了四:“输入输出流 介绍”、“多重继承”、“异常处理”和“运行时类型识别”,删去的内容均为C++中较复杂的主题,作者将它们连同C++标准完成后增加的一些内容放到本书 的第2卷中,使本书的第1卷内容显得更加集中,可以供不同程度的读者选择阅读。需要强调的是,第2版的改变不仅体现在这些章节的调整上,更多的改变体现在 每一的字里行间,包括例子的调整和练习的补充。与众不同的精心选材和认真推敲的叙述使得第2版更趋成熟。 本书是C++领域内一本权威的著作,书中的内容、讲授方、例子和练习既适合课堂教学,又适合读者自学。无论是高等院校计算机及相关专业的学生,还是业界的从业人员,以及广大的计算机爱好者,都可从阅读本书中获益。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值