第一章 面向对象的概念简介
软件行业中,技术变迁非常快,而概念则是逐步演进。
概念是相对稳定,但也在变化。同时,也经常被重新反思,引发新的讨论。
从 90 年代中后期至今,行业技术的发展都是在使用这些概念。
本书主要目标是,让你学会思考如何将面向对象概念应用于面向对象的系统设计中。
1.1 基本概念
历史上定义面向对象的语言拥有以下特点:封装(encapsulation)、继承(inheritance)和多态(polymorphism)。
作者认为面向对象的概念如下:封装(encapsulation)、继承(inheritance)、多态(polymorphism)和组合。
1.2 对象及遗留系统
面向对象与结构化编程绝不是互斥的。它们是互补的,因为对象可以与结构化代码很好地继承。
1.3 过程式编程与面向对象编程
究竟什么是对象?
这既是一个复杂的问题,也是一个简单的问题。它复杂是因为学习任何一种软件开发方法论都非易事。它简单是因为人们已经在按对象的方式进行思考。
例如,当你看到一个人,你会把他看做一个对象。一个对象由两部分组成:属性及行为。
一个人具有属性,比如眼睛颜色、年龄、身高等。一个人也有行为,比如行走、讲话、呼吸等。
对象的基本定义是一个包含了数据和行为的实体。
在面向对象的设计中,属性及行为包含在单个对象中,而在过程式或结构式设计中,属性和行为通常是分开的。
面向对象的数据库与关系型数据库
结构化编程中数据往往与程序分离,而且数据是全局的,所以在你的代码作用域之外依然可以很容易修改数据。
对象不会完全替代其他的软件开发范式,它是一种进化。
在面向对象的术语中,数据表现为属性,行为表现为方法。限制访问具体属性和(或)方法的行为叫做数据隐藏。
将属性及方法合并到同一个实体中,叫做封装(encapsulation)。
1.4 由面向过程开发过渡到面向对象开发
过程式编程通常会将系统的数据与对数据的操作分离开来。例如,通过网络发送信息,只发送相关数据/数据包,而期望网络管道另一端的程序知道如何处理该数据。换句话说,客户端和服务器端要对数据传输建立起一种握手约定。
面向对象编程的最大优势是数据和对数据的操作(代码)都被封装在一个对象中。例如,当通过网络传输对象时,整个对象(包括里面的数据和行为)都会一起被传输。
大多数情况下,行为本身不会被发送,因为两端都有行为代码的副本。
UML 类图
可视化建模工具提供了一种方式来使用统一建模语言(unified modeling language, UML)创建和操作类图。
类是创建对象的模板
1.6 究竟什么是类
类是对象的蓝图
以关系型数据库为例,在数据表中,表自身的定义(即字段、描述和数据类型)是类(即元数据),而对象则是表中的行(即数据)。
类定义了使用该类创建的所有对象具有的属性和行为。类是一块代码。可以单独分发使用类实例化的对象,也可以将类作为程序库的一部分进行分发。因为对象从类中穿件,所以类必须定义对象的基础材料(即属性、行为和消息)。
1.8 封装和数据隐藏
除了如何使用该对象,其他细节都应当对其他对象隐藏起来。
封装是基于对象既包含属性也包含行为。数据隐藏是封装的主要部分。
接口定义了对象之间通信的基本手段。每个类设计接口规格来保证对象能被正确实例化和操作。必须向对象提供的接口发送消息来使用对象暴露的任何行为。接口需要完整描述类与类之间的交互。
访问修饰符指定为 public 的方法属于接口。
类有接口,方法也有接口。类的接口是公共方法。你使用方法的签名来调用这些公共方法。方法的签名主要由方法名和它的参数列表组成。
Public class IntSquare {
// 私有属性
private int squareValue;
// 公共接口
public int getSquare (int value) {
SquareValue = caculateSquare(value);
return SquareValue;
}
// 私有实现
private int calculateSquare (int value) {
return value * value;
}
}
1.9 继承
继承是实现代码重用的主要手段。
继承允许一个类继承另一个类的属性和方法。
我们可以通过抽象公共属性和行为来创建新类。
面向对象程序设计中的一个主要设计问题就是识别多个类的共性。
超类和子类
超类,也成为父类(也叫基类),包含了继承自它的所有类的公共属性和行为。
子类,也称为孩子类(也叫衍生类),是超类的扩展。
继承的力量在于它的抽象和组织技术。
is-a 关系
在 Shape(形状)例子中,Circle(圆形)、Square(矩形)和 Star(星形)都直接继承自 Shape。这种关系通常被称为 is-a 关系。
多态
当你告诉某人画一个形状时,你被问到的第一个问题是“什么形状?”。没人能绘制一个形状,因为形状是一个抽象概念(事实上 Shape 代码中的 Draw() 方法并不包含实现)。你必须制定一个具体的形状。比如你需要为 Circle 提供具体的实现。虽然 Shape 有一个 Draw 方法,但 Circle 继承了该方法并提供了对该方法的实现。
重载(overriding)的基本释义是子类覆盖父类中的一个实现。
每个类(子类)能够对(父类的)同一个方法返回不同的响应来绘制自己。这就是多态的意义。
如果子类继承了父类的一个抽象方法,它必须提供该类方法的具体实现,否则它自身也必须是个抽象类。
Circle circle = new Circle(5);
Rectangle rectangle = new Rectangle(4, 5);
stack.push(circle);
Stack.push(rectangle);
while (!stack.empty()) {
Shape shape = (Shape) stack.pop();
System.out.println("Area = " + shape.getArea());
}
1.11 组合(has-a)
对象包含其他对象非常自然。
使用其他对象来构建或结合成新的对象,这种方式就是组合。
和继承一样,组合也是一种构建对象的机制。
只有两种方式来使用其他类构建新类,这两种方式就是继承和组合。
继承允许类继承另一个类。我们可以把属性和行为抽象到通用类中。例如,狗和猫都是哺乳公务,因为狗是(is-a)哺乳公务,猫也是(is-a)哺乳动物。而使用组合,可以把类嵌入其他类中来构造新类。
单独建模,如单独构建引擎,把它应用到各种车上,更别提还有其他优势。但我们不能说引擎是(is-a)一辆车。最好用 has-a 术语来描述组合关系。车有(has-a)引擎。
电视有(has-a)开关和显示屏。
计算机有(has-a)显卡,有(has-a)键盘,有(has-a)光驱。
电视显而易见不是一个开关,所以两者没有继承关系。
继承关系是 is-a 关系。
组合关系可以称为 has-a 关系。
总结:
封装。把数据和行为封装到单个对象中是面向对象开发中的重中之重。单个对象既包含自身的数据,也包含自身的行为,并且可以向其他对象隐藏自身的某些东西。
继承。类可以继承自另一个类,并且可以使用父类中定义的属性和方法。
多态。多态意味着相似的对象对相同的消息有着不同的响应。例如,你可能拥有一个有很多形状的系统。然而圆、正方形和星形的绘制方式不同。使用多态你可以给这些形状发送相同的消息(例如 Draw 方法),每个形状可以响应自身的绘制。
组合。组合意味着可以使用其他对象来构建新对象。