对象的特征:
(1)万物皆对象。对象是一个奇特的变量,可以存放数据,可以对其提出请求,要求其执行计算
(2)程序就是一组对象。对象之间通过发送消息互相通知做什么。将消息看做是对于调用某个特定对象所属函数的请求
(3)每一个对象都有它自己的由其他对象构成的存储区。这样,就可以通过包含已 经存在的对象创造新对象
(4)对象都有一个类型,也就是类。每个对象都是类的实例。
(5)特定类型的所有对象都能接收到相同的消息。
所有对象(虽然都具有惟一性)都是一类对象中的一员,它们有共同的特征和行为。
我们如何得到一个对象去为我们做有用工作呢?必须有一种方法能向对象作出请求,使得它能做某件事情,例如完成交易等。**可以向对象发出请求的是由它的接口进行定义的,**而接口由类型确定。
e.g. 接口举例(电灯泡)
扩展:统一建模语言(Unified Modeling Language, UML)的格式。每个类由一个方框表示,这个方框的顶部标有类型名,中间部分列出所关注的数据成员,底部是成员函数(属于这个对象的函数),它们能接收发送给这个对象的任何消息)。
把程序员划分为类创建者(创建新数据类型的人,只暴露需要的东西,其它的都隐藏起来)和客户程序员(在应用程序中使用数据类型的类的用户)
隐藏的理由:
(1)防止客户程序员插手他们不应当接触的部分
(2)允许库设计者去改变这个类的内部工作方式,这里要求接口和实现是严格分离的。这样库设计这可以修改接口的实现而不用修改接口,不会影响客户程序员的接口使用。
C++语言使用了三个明确的关键字来设置类中的边界:public、private和protected。public意味着随后的定义对所有人都可用,private关键字则意味着,除了该类型的创建者和该类型的内部成员函数之外,任何人都不能访问这些定义。而protected中继承的类可以访问,其余的和private相同
重用一个类最简单的方法就是直接使用这个类的对象,并且还可以将这个类的对象放到一个新类的里面,我们称之为“创建一个成员对象”。也称为"组合"(has-a)
实际上,当创建新类时,程序员应当首先考虑组合。新类的成员对象通常是私有的,使用这个类的客户程序员不能访问它们。是的改变成员而不会干扰到已经存在的客户代码,同时能够动态地改变程序的行为。
如果能选取已存在的类,克隆它,然后对这个克隆增加和修改,则是再好不过的事。这是继承(inheritance)带来的好处。
但是如果原来的类被修改,派生的类也会产生相应的修改。
派生类复制了基类的接口,也就是说,所有能够发送给这个基类对象的消息,也能够发送给这个派生类的对象。
当然,有时候我们希望派生类的相同接口有不同的行为,这时我们可以使用"重载"来覆盖函数,相当于说:“我正在使用同一个接口函数,但是我希望它为我的新类型做不同的事情。”
若没有先派生类的接口添加新的函数,那么我们常把基类和派生类之间的关系看做是一个“is-a(是)”关系,因为我们可以说“圆形是一个形体”。
但是,我们还发现有时必须向派生类的接口添加新函数,这个时候可以看做"is-like-a"关系,如我们将家里的只能制冷的空调换成可以制冷和制热的热泵,我们可以说是将空调的功能拓展了,但是不能说热泵是空调。
栈:内存中的一个区域,可以直接由微处理器在程序执行期间存放数据。栈中变量可以称为局部变量或者自动变量
静态存储区:是内存的一个固定块,在程序开始执行以前分配。
堆:动态创建对象,其生命周期以及准确的数据类型都是由运行的时候确定的。如果需要新的对象,直接使用new关键字让它在堆上生成。当使用结束时,用关键字delete释放。
性能比较:堆上分配的存储时间要比栈上创建的存储时间长的多(在栈上创建存储常常只是一条向下移动栈指针的微处理器指令,另外一条是移回指令)。
生命期比较:如果在栈上或在静态存储上创建一个对象,编译器决定这个对象持续多长时间并能自动销毁它。然而,如果在堆上创建它,编译器则不知道它的生命期。程序员必须编程决定何时销毁此对象。然后使用delete关键字执行这个销毁任务。作为一个替换,运行环境可以提供一个称为垃圾收集器(garbage collector)的功能,当一个对象不再被使用时此功能可以自动发现并销毁这个对象。但是垃圾收集器及其系统开销是巨大的,所以C++并没有包括它。
异常是一个对象,它在出错的地方抛出,并且被一段用以处理特定类型错误的、相应的异常处理代码(exception handler)所捕获。异常似乎是另一个并行的执行路径,在出错的时候被调用,不会干扰正常的执行代码
异常必须进行处理,而且提供了一个从错误状态中进行可靠恢复的方法,除了从这个程序中退出以外,我们常常还可以作出正确的设置,并且恢复程序执行,这有助于产生更健壮的系统。
不论做了多少分析,总有系统的一些问题直 到设计时才暴露出来,并且更多的问题是到编程或直到程序完成运行时才出现。因此,迅速进行分析和设计并对提出的系统执行测试是相当重要的。
在构造DBMS时,需要彻底理解用户的需要。我们要对两部分进行考虑:
(1)有哪些对象?(如何将项目分成多个组成部分?)
(2)它们的接口是什么?(需要向每个对象发送什么信息?)
只要我们知道了对象和接口,就可以编写程序了。由于各种原因我们可能需要比这些更多的描述和文档,但是我们需要的信息不能比这些更少。
我们必须首先决定在此过程中应当有哪些步骤。在进程中设立一些 里程碑可以帮助集中我们的注意力,激发我们的热情,而不是只注意“完成项目”这个单一的目标。另外,里程碑将项目分成更细的阶段使得风险减小
这一阶段称为“建立需 求分析(requirements analysis)和系统规范说明(system specification)”。
需求分析说的是“制定一系列指导方针,我们将利用它了解任务什么时候完成且用户什么时候满足”。
系统规范说明指出,“这是程序将做什么(不是如何做)以满足需求的一个描述”。
需求分析实际上是我们和用户之间的一个合同,系统规范说明是对问题的一个顶层探测,且在一定程度上要说明项目是否能做和将需多少时间。、
描述这些类和它们如何交互。确定类和交互的出色 技术是类职责协同(Class-Responsibility-Collaboration, CRC)卡片,其内容如下:
(1)类的名字。这很重要,因为名字体现了类行为的本质
(2)类的职责:它应当做什么。通常,它可以仅由成员函数的名字陈述(好的名字是具有可描述性的)
(3)类的协同:它与其他类有哪些交互?
对象开发准则:
1)让特定问题生成一个类,然后在解决其他问题期间让这个类生长和成熟。
2)让特定问题生成一个类,然后在解决其他问题期间让这个类生长和成熟。
3)不要强迫自己在一开始就知道每一件事情,应当不断地学习。
4)开始编程,让一些部分能够运行,这样就可以证明或否定已生成的设计。不要害怕过程型大杂烩式的代码—类的隔离性可以控制它们。
5)尽量保持简单。具有明显用途的不太清楚的对象比很复杂的接口好。从一个简单的类中添加元素比从一个复杂的类中删除元素要轻松地多
从粗线条设计向编译和执行可执行代码体的最初转换阶段,特别是,它将证明或否定我们的体系结构。通过反复的编码、集成、测试、向用户提出反馈意见,从而不断发展完善。
建立这个系统的一部分工作是实际检查,就是对照需求分析和系统规范说明与测试结果。确保我们的测试结果与需求和用例符合。当系统核心稳定后,我们就可以向下进行和增加更多的功能了。
一旦代码框架运行起来,我们增加的每一组特征本身就是一个小项目。在一次迭代 (iteration)期间,我们增加一组特征,一次迭代是一个相当短的开发时期。
一次迭代有多长时间?理想情况下,每次迭代为一到三个星期,在这个期间的最后,我们得到一个集成的、测试过的、比前一周期有更多功能的系统。
迭代的基础为一个用例,每个用例都是一组有相关功能的,在一次迭代中加入系统。
迭代的结束是以客户的满意决定的,当客户满意时就停止迭代。
传统上称为“维护”的一个阶段,包含了 从“让软件真正按最初提出的方式运行”到“添加用户忘记说明的性能”。
“我们不可能第一次就使软件正确,所以应该为学习、返工和修改留有余地”。如果我们使软件不断进化,直到使软件正确,无论在短期内 还是在长期内,将产生极为优雅的程序。进化是使程序从好到优秀,是使第一遍不理解的问题变清楚的过程。
“使软件正确”的意思不只是使程序按照要求和用例工作,还意味着我们理解代码的内部结构,并且认识到它能很好地协同工作,没有拙笨的语法和过大的对象,也没有难看的暴露的代码。
程序结构将经历各种修改而保全下来,这些修改贯穿整个生命期
“先写测试”和“结对编程”
1.10.1 先写测试
当创建测试时,我们被迫要充分思考这个类,常常会发现所需要的功能。写测试的第二个重要的作用,是能在每次编连软件时运行这些测试。这实际上让编译器又执行了测试。
1.10.2 结对编程
主张代码应当在每个工作站上由两个人编写。结对编程的好处是,一个人编写代码时另一个人在思考。思考者的头脑中保持总体概念,不仅是手头问题的这一段。如果编码者遇到障碍,他们就交换位置。如果两个人都遇到障碍,他们的讨论可能被在这个区域工作的其他人听到可能给出帮助。