理解多态在面向对象编程中的核心作用
掌握 JavaScript 中实现多态的两种主要方式:方法重写与模拟方法重载
能够在实际项目中应用多态提升代码灵活性和可扩展性
了解多态与其他 OOP 特性(如继承、抽象)的协同关系
多态源自希腊语,意为“多种形态”。在面向对象编程中,它指的是:
同一个接口可以被不同类型的对象以各自的方式实现。
多态的核心价值在于:
解耦调用者与具体实现
支持“开闭原则”(对扩展开放,对修改关闭)
提升代码的可维护性和可扩展性
在 JavaScript 中,由于其动态类型和原型继承特性,多态天然存在且易于实现。
运行时多态
这是 JavaScript 中最常见、最自然的多态形式。子类通过重写父类方法,提供特定行为。
class Animal {
speak() {
console.log("Animal makes a sound");
}
}
class Dog extends Animal {
speak() {
console.log("Dog barks");
}
}
class Cat extends Animal {
speak() {
console.log("Cat meows");
}
}
// 多态调用
const animals = [new Dog(), new Cat(), new Animal()];
animals.forEach(animal => {
animal.speak(); // 根据实际对象类型调用对应方法
});Dog barks
Cat meows
Animal makes a sound运行时决策:JavaScript 引擎在调用时动态确定使用哪个方法
无需类型检查:调用者只需知道对象有 speak() 方法
天然支持:基于原型链的继承机制自动实现
编译时多态
JavaScript 不支持传统意义上的方法重载(即同名函数因参数签名不同而共存),但可通过参数检测模拟类似行为。
class Calculator {
add(a, b) {
// 根据参数数量决定行为
if (arguments.length === 1) {
return a + a; // 单参数:翻倍
} else if (arguments.length === 2) {
return a + b; // 双参数:相加
} else {
throw new Error("Unsupported number of arguments");
}
}
// 更健壮的实现:支持任意数量参数
sum(...numbers) {
if (numbers.length === 0) return 0;
return numbers.reduce((total, num) => total + num, 0);
}
}
const calc = new Calculator();
console.log(calc.add(5)); // 10
console.log(calc.add(3, 4)); // 7
console.log(calc.sum(1, 2, 3, 4)); // 10使用默认参数或剩余参数
class MathUtils {
// 使用默认参数实现“重载”效果
multiply(a, b = a) {
return a * b;
}
// 使用类型检查实现不同行为
process(value) {
if (typeof value === 'number') {
return value * 2;
} else if (Array.isArray(value)) {
return value.map(x => x * 2);
} else if (typeof value === 'string') {
return value.toUpperCase();
}
throw new TypeError("Unsupported type");
}
}注意:这不是真正的编译时多态,而是运行时参数分发,但能达到类似效果。
子类在重写方法时,常需调用父类实现,super 提供了这种能力。
class Shape {
constructor(color) {
this.color = color;
}
draw() {
console.log(`Drawing a ${this.color} shape`);
}
}
class Circle extends Shape {
constructor(color, radius) {
super(color); // 调用父类构造函数
this.radius = radius;
}
draw() {
super.draw(); // 先执行父类逻辑
console.log(`Circle with radius: ${this.radius}`);
}
}
const circle = new Circle("red", 10);
circle.draw();输出:
Drawing a red shape
Circle with radius: 10class Component {
render() {
throw new Error("Subclass must implement render()");
}
}
class Button extends Component {
render() {
return "<button>Click me</button>";
}
}
class Input extends Component {
render() {
return '<input type="text" placeholder="Enter text">';
}
}
// 多态渲染
const components = [new Button(), new Input()];
components.forEach(comp => console.log(comp.render()));class Database {
connect() { /* 通用连接逻辑 */ }
query(sql) { throw new Error("Must be implemented"); }
}
class MySQLAdapter extends Database {
query(sql) {
console.log(`Executing MySQL query: ${sql}`);
// MySQL-specific implementation
}
}
class MongoDBAdapter extends Database {
query(filter) {
console.log(`Executing MongoDB find: ${JSON.stringify(filter)}`);
// MongoDB-specific implementation
}
}class FileHandler {
parse(content) {
throw new Error("Subclass must implement parse()");
}
}
class JSONHandler extends FileHandler {
parse(content) {
return JSON.parse(content);
}
}
class CSVHandler extends FileHandler {
parse(content) {
return content.split('\n').map(line => line.split(','));
}
}代码复用:通用逻辑写在父类,特异性逻辑由子类实现
扩展性强:新增类型无需修改现有调用代码
测试友好:可轻松使用 Mock 对象替代真实实现
架构清晰:符合“针对接口编程,而非针对实现编程”原则
定义清晰的基类接口
父类应明确声明哪些方法需要被重写
避免过度继承
优先组合而非继承;若行为差异大,考虑策略模式
使用 instanceof 谨慎
多态的目的就是避免类型检查,如需检查说明设计可能有问题
文档化预期行为
在父类方法注释中说明子类应如何实现
JavaScript 的多态主要通过方法重写实现,天然支持运行时多态
方法重载需手动模拟,可通过参数数量、类型或剩余参数实现
super 关键字允许子类在重写时复用父类逻辑
多态的核心价值是解耦,使系统更易扩展和维护
在 UI、数据处理、适配器等场景中,多态能显著提升代码质量
在 Animal 示例中,如果我们创建一个 Bird 类但忘记实现 speak() 方法,会发生什么?这体现了多态的什么特性?
为什么说“使用 instanceof 判断类型后再调用不同方法”违背了多态的设计初衷?请举例说明更好的做法。
在函数式编程范式下,如何用高阶函数或组合实现类似多态的效果?与面向对象的多态相比有何优劣?