Skip to content

1.3 面向对象编程(OOP)核心概念

在正式进入设计模式的学习之前,我们必须先花点时间了解一下“面向对象编程”(Object-Oriented Programming, OOP)的几个核心概念。

你可能会问:“JavaScript 不是一门多范式语言吗?为什么非要学 OOP?”

原因很简单:经典的设计模式诞生于一个以 OOP 为主流的时代(比如 C++ 和 Java)。这些模式的设计初衷,就是为了更好地组织和管理“对象”。因此,理解 OOP 的核心思想,是掌握大部分设计模式的钥匙。

别担心,我们不会深入复杂的理论,只会聚焦于和设计模式最相关的三大核心特性:封装、继承、多态。我们将用最直白的 JavaScript 示例来理解它们。

一、封装 (Encapsulation)

一句话定义:将数据(属性)和操作这些数据的代码(方法)捆绑到一个独立的对象中,并隐藏对象的内部细节,只对外暴露必要的接口。

生活比喻:想象一下你家的“微波炉”。 你不需要关心它内部的磁控管、变压器、电路板是如何协同工作的(隐藏内部细节)。你只需要操作面板上的那几个按钮,比如“启动”、“设置时间”、“选择火力”(暴露必要接口)。微波炉把复杂的内部实现封装起来,为你提供了简单易用的功能。

在代码中,封装意味着...

一个对象应该管理自己的状态。外部代码不能随意修改对象内部的数据,而必须通过对象提供的方法来与之交互。

JavaScript 示例:

在现代 JavaScript 中,我们可以使用 class 和私有字段 # 来实现封装。

javascript
class Wallet {
  // #balance 是一个私有字段,外部无法直接访问
  #balance;

  constructor(initialBalance) {
    this.#balance = initialBalance;
  }

  // 对外暴露的公共方法(接口)
  deposit(amount) {
    if (amount > 0) {
      this.#balance += amount;
      console.log(`存入 ${amount},当前余额: ${this.#balance}`);
    }
  }

  withdraw(amount) {
    if (amount > this.#balance) {
      console.log("余额不足!");
      return;
    }
    this.#balance -= amount;
    console.log(`取出 ${amount},当前余额: ${this.#balance}`);
  }

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

const myWallet = new Wallet(100);

// 你无法直接操作余额
// console.log(myWallet.#balance); // ❌ SyntaxError: Private field '#balance' must be declared in an enclosing class

// 只能通过我们提供的“按钮”来操作
myWallet.deposit(50); // 存入 50,当前余额: 150
myWallet.withdraw(30); // 取出 30,当前余额: 120
console.log(myWallet.getBalance()); // 120

封装的核心价值

  • 安全性:保护了内部数据不被外界随意篡改。
  • 易用性:降低了模块的复杂度,使用者无需关心内部实现,只需调用接口即可。

二、继承 (Inheritance)

一句话定义:允许一个对象(子类)获取另一个对象(父类)的属性和方法,并可以添加或重写它们。

生活比喻:你和你的父亲。 你从父亲那里继承了某些特征,比如姓氏、肤色。这是你们的共性。同时,你也有自己独特的技能和爱好,比如你会写代码而他不会。这是你的个性

在代码中,继承意味着...

我们可以创建一个通用的“父类”来定义一些基础的属性和方法,然后让多个“子类”去继承它,从而实现代码的复用。

JavaScript 示例:

javascript
// 父类:定义了所有动物的共性
class Animal {
  constructor(name) {
    this.name = name;
  }

  eat(food) {
    console.log(`${this.name} 正在吃 ${food}。`);
  }
}

// 子类:继承自 Animal
class Dog extends Animal {
  // Dog 不仅有 Animal 的所有能力...
  constructor(name) {
    super(name); // 调用父类的 constructor
  }

  // ...还有自己的新能力
  bark() {
    console.log(`${this.name} 正在汪汪叫!`);
  }
}

const myDog = new Dog("旺财");
myDog.eat("骨头"); // 继承自 Animal 的方法
myDog.bark(); // 自己的方法

继承的核心价值

  • 代码复用:将通用代码放在父类中,避免在多个子类中重复编写。
  • 逻辑清晰:建立了类与类之间的“is a”关系(狗“是”一种动物),让代码结构更符合现实世界的模型。

三、多态 (Polymorphism)

一句话定义:不同的对象,在接收到同一个消息(调用同一个方法)时,可以有不同的行为表现。

生活比喻:“移动”这个指令。 当你对“人”下达“移动”指令时,他会“用脚走”。 当你对“鱼”下达“移动”指令时,它会“摇尾巴游”。 当你对“鸟”下达“移动”指令时,它会“扇动翅膀飞”。 同一个“移动”指令(相同接口),引出了完全不同的具体行为(不同实现)。这就是多态。

在代码中,多态意味着...

我们可以编写一段通用的代码来处理不同类型的对象,而无需关心这些对象的具体类型是什么,只要它们都实现了我们需要的那个接口(方法)。

JavaScript 示例:

javascript
// 定义一组都拥有 makeSound 方法的对象
class Cat {
  makeSound() {
    console.log("喵喵喵~");
  }
}

class Dog {
  makeSound() {
    console.log("汪汪汪!");
  }
}

class Duck {
  makeSound() {
    console.log("嘎嘎嘎!");
  }
}

// 这个函数不关心传入的 animal 究竟是猫、是狗还是鸭子
// 它只知道 animal 这个对象一定有 makeSound 方法可以调用
function triggerSound(animal) {
  animal.makeSound();
}

const kitty = new Cat();
const puppy = new Dog();
const donald = new Duck();

triggerSound(kitty); // 输出: 喵喵喵~
triggerSound(puppy); // 输出: 汪汪汪!
triggerSound(donald); // 输出: 嘎嘎嘎!

多态的核心价值

  • 灵活性和可扩展性triggerSound 函数是稳定的。未来即使我们新增一个 Chicken 类,只要它也有 makeSound 方法,triggerSound 函数就无需任何修改,可以直接使用它。这大大提高了代码的可维护性。

四、总结

概念核心思想
封装隐藏内部,暴露接口
继承复用共性,发展个性
多态相同接口,不同实现

虽然 JavaScript 是一门非常灵活的语言,它的 OOP 实现(基于原型)与传统的基于类的语言有所不同,但封装、继承、多态这三大思想是通用的。

理解了这些,你就拥有了阅读设计模式这本“武功秘籍”的内功心法。现在,我们已经做好了所有准备工作。