本文不纠结语言的选择,仅仅介绍面向对象这一个编程思想的运用,以及与另一大主流的编程思想:面向过程的区别,我可以毫不夸张的说,面向对象这个词大家肯定都有所耳闻,但要真正理解它比找对象还要难!
面向对象思想是将问题领域中的实体抽象为对象,并通过定义类的属性和行为来描述和操作这些对象。类是对象的模板,而对象则是类的具体实例化结果。通过面向对象的编程,可以更好地模拟现实世界中的问题,提高代码的可读性、扩展性和复用性。
而面向过程编程更注重功能的实现和步骤的顺序,适用于解决特定的、相对简单的问题。两种范式各有优劣,选择使用哪种编程范式取决于具体的问题和需求。那就让我们举一个具体的例子:假设我们要编写一个程序来模拟汽车的行驶过程
面向对象的实现方式:
面向过程的实现方式:
对比两种实现方式:
面向对象的实现方式更符合现实中的问题描述和设计,我们将汽车作为一个独立的对象来抽象,并通过定义类的属性和行为来描述汽车。多个具体的汽车对象可以同时存在,并且可以执行各自的行为,并且可以方便地扩展和修改汽车的属性和行为。
总的来说面向对象的编程思维之所以为大多数程序员使用,还要得益于他的三大特性!以下所有代码均使用Java这个主流语言来举例。
利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体。数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外的接口使其与外部发生联系。用户无需关心对象内部的细节,但可以通过对象对外提供的接口来访问该对象。
public class Person {
private String name;
private int gender;
private int age;
public String getName() {
return name;
}
public String getGender() {
return gender == 0 ? "man" : "woman";
}
public void work() {
if (18 <= age && age <= 50) {
System.out.println(name + " is working very hard!");
} else {
System.out.println(name + " can't work any more!");
}
}
}
Person 类封装 name、gender、age 等属性,外界只能通过get()
方法获取一个 Person 对象的 name 属性和 gender 属性,而无法获取 age 属性,但是 age 属性可以供 work()
方法使用。
注意到 gender 属性使用int
数据类型进行存储,封装使得用户注意不到这种实现细节。并且在需要修改 gender 属性使用的数据类型时,也可以在不影响客户端代码的情况下进行。
继承是面向对象编程中的一个重要特性,它允许一个类(称为子类)从另一个类(称为父类或基类)继承属性和方法。通过继承,子类可以重用父类的代码,并在此基础上添加、修改或覆盖自己的特定行为。
继承实现了 IS-A 关系,例如 Cat 和 Animal 就是一种 IS-A 关系,因此 Cat 可以继承自 Animal,从而获得 Animal 非 private 的属性和方法。
继承应该遵循里氏替换原则,子类对象必须能够替换掉所有父类对象。
Cat 可以当做 Animal 来使用,也就是说可以使用 Animal 引用 Cat 对象。父类引用指向子类对象称为 向上转型 。
Animal animal = new Cat();
// 定义一个父类Animal
class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public void eat() {
System.out.println(name + " is eating.");
}
}
// 定义一个子类Dog,继承自Animal
class Dog extends Animal {
public Dog(String name) {
super(name);
}
public void bark() {
System.out.println(name + " is barking.");
}
}
// 主函数
class Main {
public static void main(String[] args) {
Dog dog = new Dog("Bobby");
dog.eat(); // 调用从父类继承的eat()方法
dog.bark(); // 调用子类自己的bark()方法
}
}
在上述示例中,Animal类作为父类,有一个属性name和一个方法eat()
。Dog类作为子类继承了Animal类,同时自己添加了一个方法bark()
。在主函数中,我们创建了一个Dog对象,并调用了从父类继承的eat()
方法和子类自己的bark()
方法。通过继承,Dog类可以重用Animal类的代码,并扩展自己的行为。
多态是面向对象编程中的一个重要概念,它允许使用不同的对象来调用相同的方法,实现了同一行为的多种表现形式。多态性提高了代码的灵活性、扩展性和可维护性。
多态分为编译时多态和运行时多态:
运行时多态有三个条件:
public class Instrument {
public void play() {
System.out.println("Instument is playing...");
}
}
public class Wind extends Instrument {
public void play() {
System.out.println("Wind is playing...");
}
}
public class Percussion extends Instrument {
public void play() {
System.out.println("Percussion is playing...");
}
}
public class Music {
public static void main(String[] args) {
List<Instrument> instruments = new ArrayList<>();
instruments.add(new Wind());
instruments.add(new Percussion());
for(Instrument instrument : instruments) {
instrument.play();
}
}
}
运行结果:
Wind is playing...
Percussion is playing...
代码中,乐器类(Instrument)有两个子类:Wind 和 Percussion,它们都覆盖了父类的 play() 方法,并且在 main() 方法中使用父类 Instrument 来引用 Wind 和 Percussion 对象。在 Instrument 引用调用 play() 方法时,会执行实际引用对象所在类的 play() 方法,而不是 Instrument 类的方法。
补充:
访问父类的构造函数:可以使用super()
函数访问父类的构造函数,从而委托父类完成一些初始化的工作。应该注意到,子类一定会调用父类的构造函数来完成初始化工作,一般是调用父类的默认构造函数,如果子类需要调用父类其它函数,那么就可以使用 super关键字。
本文详细地介绍了面向对象编程的基本概念和特性,以及类、对象、继承、封装和多态的具体定义和应用。通过这篇文章,读者可以深入了解面向对象编程的思想和方法,并能够运用它来解决实际问题。如果短时间内无法理解,真的没有关系,在写代码读优秀的代码的过程中慢慢理解这些概念,你一定会豁然开朗的!
至于一些高级的面向对象的知识,比如设计原则、设计模式、抽象类等,这些在我们真正编程的时候很少会用到,等到你成为一名真正意义上的架构师,再去系统学习也不迟,作者水平有限,这里就不涉及了。