抽象、封装、多态和继承是面向对象编程(OOP)的四大基本概念,它们构成了面向对象设计的基础。
抽象(Abstraction)
含义
抽象是从众多事物中提取共有特征,忽略非本质细节,将复杂的现实世界简化为模型的过程。它关注的是对象的本质特征,而忽略其非本质的细节。在编程中,抽象通常通过类和接口来实现,定义它们所应具备的属性和行为,但不关注具体实现。类定义对象的属性和行为,接口定义类应具备的能力。通过抽象,我们可以专注于对象的核心特征,而无需考虑所有的细节。
抽象通过定义抽象类(包含纯虚函数的类)来实现,抽象类不能被实例化,必须由子类来实现其纯虚函数。
作用
抽象通过提取共同的特征(属性和行为),形成类或接口,从而实现代码的模块化和复用。因此,抽象使得程序员可以创建高度通用的代码,而不必关心具体对象的具体实现。例如,在Java中,抽象类和接口都用于定义抽象的行为,具体的实现则由子类来完成。
举例
设想我们要模拟一个“动物”系统。动物有许多种类,如猫、狗等。所有动物都有“吃”、“睡”、“喊叫”等行为,但不同的动物会以不同的方式执行这些行为。通过抽象,我们可以定义一个Animal抽象类,包含这些通用行为,而具体的实现由各个子类(如Cat、Dog)来完成。
//C++
#include <iostream>
// 抽象类
class Animal {
public:
virtual void sound() = 0; // 纯虚函数,抽象方法,没有具体实现
};
//子类 1
class Dog : public Animal {
public:
void sound() override {
std::cout << "Woof!" << std::endl;
}
};
//子类 2
class Cat : public Animal {
public:
void sound() override {
std::cout << "Meow!" << std::endl;
}
};
int main() {
Dog dog;
Cat cat;
dog.sound(); // 输出: Woof!
cat.sound(); // 输出: Meow!
return 0;
}
//java
abstract class Animal {
abstract void sound(); // 抽象方法
}
class Dog extends Animal {
void sound() {
System.out.println("Woof!");
}
}
class Cat extends Animal {
void sound() {
System.out.println("Meow!");
}
}
public class Main {
public static void main(String[] args) {
Animal dog = new Dog();
Animal cat = new Cat();
dog.sound(); // 输出: Woof!
cat.sound(); // 输出: Meow!
}
}
这里的 Animal 类就是一个抽象类,定义了所有动物应该具备的行为,但没有具体实现,由其子类 Dog 类和 Cat 类来实现。
封装 (Encapsulation)
含义
封装是将对象的状态(属性)和行为(方法)隐藏在类内部。只通过公开的方法(即接口)来访问和修改这些属性。封装的目的是保护对象的内部状态,避免外部代码直接访问和修改对象数据,从而造成不一致或错误。
作用
封装通过访问修饰符(如private、protected、public)来控制对象内部数据的访问权限。将数据成员设为私有(private),并通过公有(public)的成员函数(方法)来访问和修改它们,从而实现数据隐藏。这样,类可以通过公开的方法来提供对数据的访问,而不暴露数据的具体实现,增强了代码的安全性和可维护性。
举例
“人”类的属性(数据成员),例如名字和年龄,为了保证其不随意被外部代码修改,因此设为私有(private),同时提供公有(public)的成员函数(方法),例如 getter 和 setter 方法来实现对这些成员变量的访问和修改,从而实现数据隐藏。
//C++
#include <iostream>
#include <string>
class Person {
private:
std::string name;
int age;
public:
void setName(const std::string& n) {
name = n;
}
std::string getName() const {
return name;
}
void setAge(int a) {
if (a > 0) {
age = a;
}
}
int getAge() const {
return age;
}
};
int main() {
Person person;
person.setName("Alice");
person.setAge(30);
std::cout << "Name: " << person.getName() << std::endl;
std::cout << "Age: " << person.getAge() << std::endl;
return 0;
}
//java
class Person {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
if (age > 0) {
this.age = age;
}
}
public int getAge() {
return age;
}
}
public class Main {
public static void main(String[] args) {
Person person = new Person();
person.setName("Alice");
person.setAge(30);
System.out.println("Name: " + person.getName());
System.out.println("Age: " + person.getAge());
}
}
在这个示例中,name和age属性是私有的,只能通过公有的set和get方法来访问。
多态 (Polymorphism)
含义
多态是指同一操作在不同对象上可以表现出不同的行为。多态可以通过函数重载、运算符重载和虚函数来实现。多态的核心是通过父类或接口的引用指向子类对象,并通过使用基类指针或引用来调用子类的重写函数。在使用继承时,多态允许我们以父类的形式处理子类对象,但可以根据实际对象的类型调用相应的方法。
作用
多态增强了程序的灵活性和可扩展性,使得相同的接口可以用于不同类型的对象。在Java中,多态通常通过方法重写(Override)和接口实现来实现,即子类可以根据自己的需要修改父类的方法,实现不同的行为,这使得代码更加动态和通用。
举例
Shape类作为父类,其有一个draw方法。具体的形状如Circle和Square都可以继承这个类,并提供各自的draw方法。我们可以通过多态,在不关心具体形状的情况下,调用形状的draw方法,再由具体形状如Circle和Square决定绘制的内容。
//C++
#include <iostream>
//父类
class Shape {
public:
//绘画函数
virtual void draw() const = 0; // 纯虚函数,抽象方法
};
//子类 1
class Circle : public Shape {
public:
void draw() const override { //绘制圆形
std::cout << "Drawing a circle." << std::endl;
}
};
//子类 2
class Square : public Shape {
public:
void draw() const override { //绘制方形
std::cout << "Drawing a square." << std::endl;
}
};
//接口函数
void renderShape(const Shape& shape) {
shape.draw(); // 根据传入对象的类型调用相应的方法
}
//接口函数的另一种写法
/*void renderShape(Shape* shape) {
shape->draw(); // 根据实际对象调用不同的draw方法
}*/
int main() {
Circle circle;
Square square;
renderShape(circle); // 输出: Drawing a circle.
renderShape(square); // 输出: Drawing a square.
return 0;
}
//java
class Shape {
void draw() {
System.out.println("Drawing a shape.");
}
}
class Circle extends Shape {
void draw() {
System.out.println("Drawing a circle.");
}
}
class Square extends Shape {
void draw() {
System.out.println("Drawing a square.");
}
}
public class Main {
public static void main(String[] args) {
Shape shape1 = new Circle();
Shape shape2 = new Square();
shape1.draw(); // 输出: Drawing a circle.
shape2.draw(); // 输出: Drawing a square.
}
}
在这个例子中,renderShape函数接受一个Shape指针,但可以根据传入的具体对象(如Circle或Square)调用不同的draw方法,也即draw方法在不同的形状对象上有了不同的表现。
继承 (Inheritance)
含义
继承是指一个类(子类)从另一个类(父类)获取属性和方法的过程。子类不仅可以继承父类的行为,还可以根据需要扩展或修改父类的行为。即,子类不仅可以直接使用(复用)父类的成员函数,还可以添加自己的成员函数。继承有助于代码的重用和层次化设计。
作用
继承使得代码的复用性大大提高,减少了重复代码的编写。它还支持类之间的层次化设计,使得类的结构更清晰。例如,在Java中,子类使用extends关键字继承父类,并可以重写父类的方法。
举例
Vehicle类作为父类,它有“启动”的行为。现在,我们想创建一个Car类,除了启动外,还可以鸣笛。我们可以让Car类继承Vehicle类,这样它就自动获得了启动的功能,只需要额外添加鸣笛的功能即可。
//C++
#include <iostream>
//父类
class Vehicle {
public:
void start() { // 车辆启动方法
std::cout << "Vehicle started." << std::endl;
}
};
//子类
class Car : public Vehicle {
public:
void honk() { // 鸣笛方法
std::cout << "Car horn honking." << std::endl;
}
};
int main() {
Car car;
car.start(); // 调用父类的方法
car.honk(); // 调用子类的方法
return 0;
}
//java
class Vehicle {
void start() {
System.out.println("Vehicle started.");
}
}
class Car extends Vehicle {
void honk() {
System.out.println("Car horn honking.");
}
}
public class Main {
public static void main(String[] args) {
Car car = new Car();
car.start(); // 调用父类的方法
car.honk(); // 调用子类的方法
}
}
在这个例子中,Car类继承了Vehicle类,因此可以使用start方法,还增加了自己的honk方法。
总结
抽象:关注的是“做什么”,而不关心“怎么做”。强调的是对对象共性的提取,隐藏细节,定义行为接口。
封装:是“隐藏细节,暴露接口”,通过控制访问来保护数据。强调的是数据保护,通过隐藏实现细节,控制外部对数据的访问。
多态 :是“一个接口,多种表现”,使得代码可以对不同的对象进行通用处理。强调的是操作的通用性,在不改变接口的前提下表现出多种行为。
继承 :是“复用和扩展”,通过共享父类的特性,减少代码重复。强调的是代码的复用,通过父子类关系共享行为和属性。
这些概念相辅相成,共同构成了面向对象编程的核心思想,使代码更加模块化、易于维护和扩展。
·····杂谈··········
抽象说:我们需要定义一个接口
封装说:我们要掩藏数据,只让外人看到这个接口
多态说:我们要针对这个接口,有多种表现行为
继承说:我们不仅可以使用基类的接口,还可以自己定义接口
·····················