Appearance
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 实现(基于原型)与传统的基于类的语言有所不同,但封装、继承、多态这三大思想是通用的。
理解了这些,你就拥有了阅读设计模式这本“武功秘籍”的内功心法。现在,我们已经做好了所有准备工作。