面向对象程序设计复习笔记

作为一个软件工程的大三学生,如果要求我立刻回答“面向对象程序设计是什么?”、“面向对象的特性是什么?”等问题,我想我是难以做到的。这是因为我们专业的“面向对象程序设计”这一门课程后有一个“(Java)”,在上这门课的时候我们可能更多地关注 Java 语法,而忽略了面向对象程序设计思想的一些精髓。实际上这些精髓映射出的是软件工程发展过程的智慧成果,只是当时我们什么也不懂。而面向对象程序设计的思想及设计模式的韵味则需要更大的代码量才能一探究竟。总之,这篇复习笔记会比较简洁地重温一下这些精髓的概念,真正去理解面向对象的思想,才会发现这些概念的设计精妙之处。

概念重温

  • 对象:一个自包含的实体,用一组可识别的特性和行为来标识。
  • :具有相同的属性和功能的对象的抽象的集合。
  • 实例化:创建对象的过程。
  • 方法重载:提供创建同名的多个方法的能力,但这些方法需使用不同的参数类型。好处是在不改变原方法的基础上,新增功能。

面向对象

面向对象的程序是由对象组成的,每个对象包含对用户公开的特定功能部分和隐藏的实现部分。在 OOP 中,不必关心对象的具体实现,只要能够满足用户的需求即可

传统的结构化程序设计通过设计一系列的过程(即算法)来求解问题,首先确定如何操作数据,然后再决定如何组织数据。而 OOP 则把数据放在第一位,然后再考虑操作数据的算法。

面向对象的好处

封装、继承、多态面向对象的三大特性降低了程序的耦合度,从而存在以下优点:

  • 可维护
  • 可复用
  • 可扩展
  • 灵活性好

封装

封装(encapsulation)指每个对象都包含它能进行操作所需要的所有信息,因此对象不必依赖其他对象来完成自己的操作。

实现封装的关键在于绝对不能让类中的方法直接地访问其他类的实例域,程序仅通过对象的方法与对象数据进行交互。

优点:

  1. 良好的封装能减少耦合;
  2. 类内部的实现可以自由修改;
  3. 类具有清晰的对外接口。

继承

继承(inheritance)定义了类如何相互关联,共享特性。继承的工作方式是,定义父类和子类,其中子类不但继承父类所有特性,还可以定义新的特性。

在 Java 中,继承通过关键字extends实现。有些语言(如 C++)允许一个类有多个父类(称为多继承),而 Java 不支持多继承,而是选择用接口提供多继承的好处,并避免多继承的复杂性和低效性。

如果子类继承于父类:

  1. 子类拥有父类非private的属性和功能;
  2. 子类具有自己的属性和功能;
  3. 子类还可以以自己的方式实现父类的功能(方法重写)。

优点:

  1. 使得所有子类公共部分都放在父类,使得代码得到共享,避免重复;
  2. 继承可使得修改或扩展继承而来的实现都较为容易。

缺点:

  1. 继承是一种类与类之间强耦合的关系。父类变,子类不得不变;
  2. 继承会破坏保障,父类实现细节暴露给子类。

如果想要阻止某个类或方法被继承,可以使用final修饰符。

多态

多态表示不同的对象可以执行相同的动作,但要通过它们自己的实现代码来执行。

  1. 子类以父类的身份出现;
  2. 子类在工作时以自己的方式来实现;
  3. 子类以父类的身份出现时,子类特有的属性和方法不可以使用。

多态的原理是当方法被调用时,无论对象是否被转换为其父类,都只有位于对象继承链最末端的方法实现会被调用。

实现多态的技术称为动态绑定(dynamic binding),是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。

  • 静态绑定发生在编译时期,动态绑定发生在运行时;
  • 使用privatestaticfinal修饰的变量或者方法,使用静态绑定。而虚方法(可以被子类重写的方法)则会根据运行时的对象进行动态绑定;
  • 静态绑定使用类信息来完成,而动态绑定则需要使用对象信息来完成;
  • 重载(Overload)的方法使用静态绑定完成,而重写(Override)的方法则使用动态绑定完成。

抽象类

Java 中,使用abstract可以声明一个抽象方法。为了提高程序的清晰度,包含一个或多个抽象方法的类本身必须被声明为抽象类。除了抽象方法之外,抽象类还可以包含具体数据和具体方法。

1
2
3
4
5
6
7
8
9
10
abstract class Person {
private String name;
public Person(String n) {
name = n;
}
public abstract String getDescription(); // 无需实现
public String getName() {
return name;
}
}

抽象方法充当着占位的角色,具体实现在子类中。若子类没有全部实现抽象方法,则子类也必须被声明为抽象类。

抽象类不能被实例化,但是可以定义一个抽象类的对象变量并引用非抽象子类的对象:

1
Person p = new Student("Kyon Huang", "Software Engineering");

接口

接口(interface)是对类的一组需求描述,主要用来描述类具有什么功能,而并不给出每个功能的具体实现。一个类可以实现(implement)一个或多个接口,并在需要接口的地方,随时使用实现了相应接口的对象。

例如,Arrays类中的sort方法承诺可以对对象数组进行排序,但前提是对象所属的类必须实现了Comparable接口。Comparable接口的代码如下:

1
2
3
4
public interface Comparable<T> {
// 接口中所有方法自动为 public,不必提供关键字
int compareTo(T other);
}

为了让类实现一个接口,通常需要下面两个步骤:

  1. 将类声明为实现给定的接口(使用关键字implements);
  2. 对接口中的所有方法进行定义。

在上例中,让Employee类实现Comparable接口,则有:

1
2
3
4
5
class Employee implements Comparable<Employee> {
public int compareTo(Employee other) {
return Double.compare(salary, other.salary);
}
}

接口不是类,不能使用new运算符实例化一个接口。但是可以声明接口的变量,并引用实现了接口的类对象。

参考资料

  • 《Java 核心技术卷 I》
  • 《大话设计模式》