面向对象编程思想

OOP 的四大特性及 JavaScript 实现

问题

什么是面向对象编程(OOP)?它有哪些特性?在 JavaScript 中如何实现?

解答

面向对象编程是一种以”对象”为中心的编程范式,通过封装、继承、多态、抽象来组织代码。

1. 封装

将数据和操作数据的方法绑定在一起,隐藏内部实现细节。

class BankAccount {
  #balance = 0; // 私有属性

  deposit(amount) {
    if (amount > 0) {
      this.#balance += amount;
    }
  }

  withdraw(amount) {
    if (amount > 0 && amount <= this.#balance) {
      this.#balance -= amount;
      return amount;
    }
    return 0;
  }

  getBalance() {
    return this.#balance;
  }
}

const account = new BankAccount();
account.deposit(100);
account.withdraw(30);
console.log(account.getBalance()); // 70
// console.log(account.#balance); // 报错,无法直接访问私有属性

2. 继承

子类继承父类的属性和方法,实现代码复用。

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} makes a sound`);
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name); // 调用父类构造函数
    this.breed = breed;
  }

  speak() {
    console.log(`${this.name} barks`);
  }

  fetch() {
    console.log(`${this.name} fetches the ball`);
  }
}

const dog = new Dog('Buddy', 'Golden Retriever');
dog.speak(); // Buddy barks
dog.fetch(); // Buddy fetches the ball

3. 多态

不同对象对同一方法有不同的实现。

class Shape {
  getArea() {
    throw new Error('子类必须实现 getArea 方法');
  }
}

class Rectangle extends Shape {
  constructor(width, height) {
    super();
    this.width = width;
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

class Circle extends Shape {
  constructor(radius) {
    super();
    this.radius = radius;
  }

  getArea() {
    return Math.PI * this.radius ** 2;
  }
}

// 同一个方法,不同的行为
function printArea(shape) {
  console.log(shape.getArea());
}

printArea(new Rectangle(4, 5)); // 20
printArea(new Circle(3));       // 28.274...

4. 抽象

提取共同特征,隐藏复杂实现。JavaScript 没有原生抽象类,可以模拟实现。

class Database {
  constructor() {
    if (new.target === Database) {
      throw new Error('Database 是抽象类,不能直接实例化');
    }
  }

  // 抽象方法
  connect() {
    throw new Error('子类必须实现 connect 方法');
  }

  query(sql) {
    throw new Error('子类必须实现 query 方法');
  }
}

class MySQL extends Database {
  connect() {
    console.log('Connected to MySQL');
  }

  query(sql) {
    console.log(`MySQL executing: ${sql}`);
  }
}

class MongoDB extends Database {
  connect() {
    console.log('Connected to MongoDB');
  }

  query(sql) {
    console.log(`MongoDB executing: ${sql}`);
  }
}

// const db = new Database(); // 报错
const mysql = new MySQL();
mysql.connect(); // Connected to MySQL
mysql.query('SELECT * FROM users'); // MySQL executing: SELECT * FROM users

关键点

  • 封装:使用 # 定义私有属性,通过公共方法访问
  • 继承extends 继承父类,super 调用父类构造函数和方法
  • 多态:子类重写父类方法,同一接口不同实现
  • 抽象:通过 new.target 和抛出错误模拟抽象类
  • 优势:提高代码复用性、可维护性和可扩展性