1.面向对象范型是为了应对使用标准化结构程序设计遇到的诸多挑战应运而生的。
2.功能分解是处理复杂问题的自然方法。解决小问题比解决整个问题要简单。
功能分解方法通常会让一个主程序控制子程序,但可能出现的问题是:主程序承受的责任太多了,经常会产生复杂的代码。如果让一些子函数负责自己的行为,并且告知主函数执行某些任务,这就是委托。
另一个问题是,它艰难的应对变化。变化到来时,它可能要修改大块函数或模块。而且变化还会为出现bug和意料之外的结果创造机会。许多隐错都源于代码修改而产生的不良副作用。
无论工作多么努力,分析多么好,对于阻止变化,我们无计可施。但是我们能够猜到一些可能会变化的地点。与其抱怨需求变化,不如改善开发过程,有效地应对变化。
3.模块化有助于代码的可理解性和可维护性,但模块化不总是有助于应对所有可能遇到的变化。
内聚性 cohesion:例程中操作之间联系的紧密程序。高内聚,低内聚(weak cohesion)。也有人将内聚性称为清晰性,clarity。极端情况下,低内聚的类对象被称为上帝对象,能做任何事情。
耦合性 coupling: 两个例程这间联系的紧密程序。 松耦合,紧耦合(tight coupling)
内聚性和耦合性是相辅相成的关系。
在维护和调试中所花费的大多数时间不是在修正隐错上,而是在弄清代码的动作机理,寻找隐错和防止出现不良副作用上了。
4.责任转移:对象可以定义自己负责自己的事物,对象天生就知道自己的类型。
面向对象范型编写代码时是围绕对象而非函数进行组织的。在面向对象范型中总是从概念,规约,实现三个视角层次思考问题。
局限的对象观。
对象是具有责任的东西,对象应该自己负责自己,应该清楚的定义责任。
在概念层次上,对象是一组责任;
在规约层次上,对象是一组可以被调用的方法;
在实现层次上,对象是代码和数据,以及它们之间的计算交互。
糟糕的是,多数人对面向对象设计的关注只停留在实现层次,只考虑代码和数据。
我们需要一个能包容多种类型的一般类型,它就是抽象类。而具体类是一个概念特定,不变的实现。
在概念层次上,抽象类是其它类的占位符。让我们能够将一组具体类看成一个概念。
在实现层次上,抽象类是不能实例化的。
5.死死盯住垒球:把注意力集中在主体部分,不要困为次要的细节而分散注意力。
分析陷阱:在开发过程中过早的深入细节。
过分依赖继承将带来超出正常的维护成本。
1.类的三大基本特征:
(1)封装,将实现细节放在一起,并将它们与抽象(公有接口)分开。
封装一般意味着各种隐藏。数据隐藏就是一种封装,将实现的细节隐藏在私有部分中。
(2)继承
(3)多态
注:类没有重载特征。
2.覆盖和重载:
覆盖是指子类重新定义父类的虚函数的做法。
重载是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。
重载的概念并不属于“面向对象编程”,重载的实现是:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的(记住:是静态)。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!
真正和多态相关的是“覆盖”。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态(记住:是动态!)的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚邦定)。
重载只是一种语言特性,与多态无关,与面向对象也无关
3.调用顺序:
父类构造函数
成员对象构造函数
派生类构造函数
派生类析构函数
成员对象析构函数
父类析构函数
4.多态:polymorphism,含义是具有多种形态。
定义为“ 把不同的特殊行为,与单个泛化记号相关联”。
有两种分类:
(1)动态多态:常见的通过类继承和虚函数机制生效于运行期的动态多态(dynamic polymorphism);当谈及多态时,如果没有明确所指,默认就是动态多态.
(2)静态多态:使用模板允许将不同的特殊行为和单个泛化记号相关联,由于这种关联处理于编译期而非运行期,因此被称为静态多态(static polymorphism);
静态多态则是指基于模板的多态;
5.父类中的重载方法必须在子类中被全部重写,否则用父类指针调用时这些方法时,不会执行到子类中部分重写的方法中。
6.向看待面向对象设计的全新方式:从理解设计模式的角度看待面向对象设计。
面向对象设计的3个基本概念:对象,封装,抽象类。
6.1.对象
传统看法:对象被视为具有方法的数据,有些人称它为智能数据,这是从实现视角看待对象。
新看法:从概念视角出发,对象是一个具有责任的实体,这些责任定义了对象的行为。
关注对象的意图行为,而不是关注对象如何实现,即关注动机而非实现,这是设计模式中反复出现的主题。
结果: 将实现隐藏在接口之后,实际上是将对象的实现与使用它们的对象解耦了。
这帮助我们免于过早的操心实现细节,从而将这些细节隐藏起来,进而能够帮助我们构建出未来更加容易修改的软件。
6.2.封装
传统看法:封装定义为数据隐藏。这样看待有很大的局限性,就像把汽车看成伞。
新看法:封装定义为任何形式的隐藏:可以隐藏数据,隐藏实现细节,隐藏派生类型,隐藏设计细节,隐藏实例化规则。
类型封装:类型的封装是通过多态,使用抽象类或接口来达到封装的。
结果:以这种更宽泛的方式看待封装,能够带来更好的切分程序的方式,封装层就成为设计需要遵循的接口。
6.3.在客户代码中使用类型封装
传统方式:在面向对象范型的早期,"类的复用"的优势曾被大力鼓吹。大量使用基类->特化类的继承模式。
对每种新情况都特化出一个类,而每个不同方面的各种情况开始交叉。弱内聚,复用差,伸缩差的问题开始暴露。
当一个类处理越多的不同变化,比如通过开关变量,时,内聚性就变的很差。即处理的特殊情况越多,可理解性就越差。
更好的方式:考虑设计中的哪些地方可能变化,它的思考点是怎样才能在不重新设计的情况下进行改变,而不是什么会迫使设计改变。
发现变化并将其封装,这是许多设计模式的主题。
用对象的属性包含变化,和用对象的行为包含变化非常相似。
6.4.设计的两步法:共性和可变性分析
传统上寻找对象的范型:在问题中使用看名词和动词的方法创建对象。
共性定义了需要使用的抽象类,可变性将成为抽象类的派生类,这些类的接口对应于规约视角。
可变性只有在给定了共性之后才有意义。
7.面向对象的一些基本原则:
对象对自己负责;
找到变化并封装之;
优先使用对象聚集,而不是类继承: 相比这下,继承类需要深入了解父类,有可能带来冗余和职责不清的问题。
针对接口编程,而不是实现;
相同功能下,减少了代码的数量,也减少了出错的机率。代码越少,问题越少。
约定优于配置
可维护,可复用,可扩展,灵活性好。