JavaScript 中的继承和组合

继承

继承是面向对象编程核心概念之一,可以帮助我们避免代码重复。主要的思想是我们可以创建一个包含逻辑的基类,可以被子类重用。

  • 特性:
    • JavaScript 类继承使用 extends 关键字。
    • 继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。
    • super() 方法用于调用父类的构造函数。
    • 当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类(父类),新建的类称为派生类(子类)。
  • 示例:
    // 基类
    class Animal {
        eat() {}
        sleep() {}
    };
    
    //派生类
    class Dog extends Animal {};
    class Cat extends Animal {};
    
    我们创建了一个基类 “Animal ”,子类会继承 Animal 中的通用逻辑。
    继承存在 is-a 关系:Dog 是一个 Animal , Cat 也是一个 Animal

组合

和继承不同,组合使用的是 has-a 关系,将不同的关系收集到一起。

class Car {
  constructor(engine, transmission) {
    this.engine = engine;
    this.transmission = transmission;
  }
}
class Engine {
  constructor(type) {
    this.type = type;
  }
}
class Transmission {
  constructor(type) {
    this.type = type;
  }
}
const petrolEngine = new Engine('petrol');
const automaticTransmission = new Engine('automatic');
const passengerCar = new Car(petrolEngine, automaticTransmission) ;

我们创建了使用 EngineTransmission 创建了 Car,我们不能说 Engine 是一个 Car,但是可以说 Car 包含 Engine

区别

我们再来看两个不同的示例,对比一下使用类的方法实现继承函数方法实现组合有什么区别。

假设我们正在使用文件系统,想实现读取、写入和删除的功能。我们可以创建一个类:

class FileService {
  constructor(filename) {
      this.filename = filename;
  } 
  read() {}
  write() {}
  remove() {}
}

目前可以满足我们想要的功能,之后我们可能想加入权限控制,一些用户只有读取权限,其他人可能有写入权限。我们应该怎么办?一个解决方案是我们可以把方法划分为不同的类:

class FileService {
  constructor(filename) {
    this.filename = filename;
  }
}
class FileReader extends FileService {
  read() {}
}
class FileWriter extends FileService {
  write() {}
}
class FileRemover extends FileService {
  remove() {}
}

目前可以满足我们的需求,但是如果有一些人既有读取又有写入的权限呢?或是只有读取和删除的权限呢?使用当前的实现,我们做不到,应该怎么解决?

我们可能会想到为读取和写入创建一个类,为读取和删除创建一个类。

class FileReaderAndWriter extends FileService {
  read() {}
  write() {}
}
class FileReaderAndRemover extends FileService {
  read() {}
  remove() {}
}

但按照这种做法,我们可能还需要以下类: FileReader, FileWriter, FileRemove, FileWriterAndRemove, FileReaderAndRemove

这不是一个好的实现方式:第一,我们可能不仅有 3 种,而是 1020 种方法,还需要在他们之间有大量的组合。第二是我们的类中存在重复的逻辑,FileReader 类包含读取方法,FileReaderAndWriter 也包含同样的代码。

这不是一个很好的解决方案,还有其他的实现方法吗?多重继承?JavaScript 中没有这个特性,而且也不是很好的方案:A 类继承了 B 类,B 类可能继承了其他类…,这样的设计会非常混乱,不是一个良好的代码架构。

怎么解决呢?一个合理的方法是使用组合:我们把方法拆分为单独的函数工厂。然后根据需求使用它们:

class FileService {
    constructor(filename) {
        this.filename = filename ;
    }
}
function createReadFileService (filename ) {
    const file = new FileService(filename);
    return {
        ...file,
        read()
    }
}
function createWriteFileService (filename) {
    const file = new FileService(filename);
    return {
        ...file,
        writer(),
    }
}

上面的例子中,我们分别创建了读取和写入服务,如果我们想组合两种权限:读取和写入,我们可以很容易的做到:

function createReadAndWriteFileService (filename) {
    const file = new FileService(filename);
	return {
	    ...file,
	    read(),
	    writer()
		}
}
const fileForReadAndWriter = createReadAndWriteFileService('test');
fileForReadAndWriter.read();
fileForReadAndWriter.write();

如果我们有 51020 种方法,我们可以按照我们想要的方式进行组合,不会有重复的代码问题,也没有令人困惑的代码架构。

我们再来看一个使用函数的例子,假设我们有很多员工,有出租车司机、健身教练和管理员。

function createDriver(name) {
    return {
        name,
        age,
        canDrive: true,   
    }
}
function createManager(name) {
    return {
        name,
        age,
        canManage: true
    }
}
function createSportCoach(name) {
    return {
        name,
        age,
        canSport: true
    }
}

看起来没有问题,但是假设有一些员工白天当健身教练,晚上去跑出租,或者可能有多种类型混合的情况,我们应该怎么办呢?就像第一个案例一样,使用组合是一种不错的方案:

function createEmployee(name,age) {
    return {
        name,
        age
    }
}
function createDriver() {
    return {
        canDrive: true
    }
}
function createManager() {
    return {
        canManage: true
    }
}
function createSportCoach() {
    return {
        canSport: true
    }
}

现在我们可以根据需要组合所有工作类型,没有重复代码,也更容易理解:

const driver = {
    ...createEmployee('Amy', 20),
    ...createDriver()
}
const manager = {
    ...createEmployee('Max', 25),
    ...createManager()
}
const sportCoach = {
    ...createEmployee('Bob', 23),
    ...createSportCoach()
}
const sportCoachAndDriver = {
    ...createEmployee('Tom', 27) ,
    ...createDriver(),
    ...createSportCoach() 
}

总结

一般来说,继承可以用于 is-a 关系,组合可以用于has-a

但继承有时候并不是一个好的解决方法:就像示例中,司机是员工(is-a关系),经理也是员工,如果我们需要把不同的部分进行混合,组合确实比继承更合适。

继承组合都是很好实现,我们应该正确的使用他们。一些场景组合可能更合适,反之亦然。

实际上,有时将继承组合结合在一起是一个不错的选择,比如我们有 is-a 关系,但想添加不同的值或方法:我们可以创建一些基类,为实例提供所有通用功能,然后使用组合来添加其他特定功能。

END

参考文章:JavaScript中的继承和组合JavaScript 类继承

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JavaScript ,实现继承方法有以下几种方式: 1. 原型继承:利用原型链实现继承。通过将父类的实例作为子类的原型,子类就可以访问到父类的属性和方法。例如: ```javascript function Parent() { this.name = 'parent'; } Parent.prototype.sayName = function() { console.log(this.name); } function Child() {} Child.prototype = new Parent(); var child = new Child(); child.sayName(); // 输出:parent ``` 2. 构造函数继承:利用 call 或 apply 方法将父类构造函数的作用域赋给子类。这种方式可以实现多继承。例如: ```javascript function Parent(name) { this.name = name; } function Child(name) { Parent.call(this, name); } var child = new Child('child'); console.log(child.name); // 输出:child ``` 3. 组合继承:结合原型继承和构造函数继承,既可以继承父类的属性和方法,又可以实现子类实例的独立。例如: ```javascript function Parent(name) { this.name = name; } Parent.prototype.sayName = function() { console.log(this.name); } function Child(name, age) { Parent.call(this, name); this.age = age; } Child.prototype = new Parent(); Child.prototype.constructor = Child; var child = new Child('child', 18); console.log(child.name); // 输出:child console.log(child.age); // 输出:18 child.sayName(); // 输出:child ``` 4. 原型式继承:利用 Object.create() 方法创建一个新对象,以某个对象为原型,然后再对新对象进行修改。例如: ```javascript var parent = { name: 'parent', sayName: function() { console.log(this.name); } }; var child = Object.create(parent, { name: { value: 'child' } }); child.sayName(); // 输出:child ``` 5. 寄生式继承:与原型式继承类似,但是在增强对象的方法时使用了一个函数封装。例如: ```javascript var parent = { name: 'parent', sayName: function() { console.log(this.name); } }; function createChild(original) { var child = Object.create(original); child.sayName = function() { console.log('hello, ' + this.name); } return child; } var child = createChild(parent); child.sayName(); // 输出:hello, parent ``` 需要注意的是,在实现继承时应该注意避免出现属性和方法的重复定义,以及避免在父类的原型上修改引用类型的属性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值