继承与组合都是面向对象中代码复用的形式,理解各自有什么特点,能够让咱们写出更简洁的代码,设计出更好的代码架构。

这是一篇翻译文章,作者是serhiirubets

“我应该怎么应用继承和组合”这是一个常见的问题,不仅是JavaScript相干,然而本篇咱们只探讨JavaScript相干的内容和示例。

如果你不晓得什么是组合继承,我强烈推荐你去查看相干内容,因为本文的次要讲的就是怎么应用和如何抉择它们,然而为了确定咱们在一个频道,让咱们先理解一下组合和继承吧。

继承是面向对象编程外围概念之一,能够帮忙咱们防止代码反复。次要的思维是咱们能够创立一个蕴含逻辑的基类,能够被子类重用。咱们来看个示例:

class Element {  remove() {}  setStyles() {}}class Form extends Element {}class Button extends Element {}

咱们创立了一个基类“Element”,子类会继承Element中的通用逻辑。

继承存在is-a关系:Form是一个Element, Button也是一个Element

组合:和继承不同,组合应用的是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, FileReaderAndWriter, FileReaderAndRemover。

这不是一个好的实现形式:第一,咱们可能不仅有3种,而是10、20种办法,还须要在他们之间有大量的组合。第二是咱们的类中存在反复的逻辑,FileReader类蕴含读取办法,FileReaderAndWriter也蕴含同样的代码。

这不是一个很好的解决方案,还有其余的实现办法吗?多重继承?JavaScript中没有这个个性,而且也不是很好的计划:A类继承了B类,B类可能继承了其余类...,这样的设计会十分凌乱,不是一个良好的代码架构。

怎么解决呢?一个正当的办法是应用组合:咱们把办法拆分为独自的函数工厂。

function createReader() {    return {        read() {            console.log(this.filename)        }    }}function createWriter() {    return {        write() {            console.log(this.filename)        }    }}

下面的示例中,咱们有两个函数创立了能够读取和写入的对象。当初就能够在任何咱们中央应用它们,也能够将它们进行组合:

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

下面的例子中,咱们创立了读取和写入服务,如果咱们想组合不同的权限:读取、写入和删除,咱们能够很容易的做到:

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

如果咱们有5、10、20种办法,咱们能够依照咱们想要的形式进行组合,不会有反复的代码问题,也没有令人困惑的代码架构。

咱们再来看一个应用函数的例子,假如咱们有很多员工,有出租车司机、健身教练和司机:

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

看起来没有问题,然而假如有一些员工白天当健身教练,早晨去跑出租,咱们应该怎么调整呢?

function createDriverAndSportCoach(name) {    return {        name,        canSport: true,        canDriver: 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('Alex', 20),    ...createDriver()}const manager = {    ...createEmployee('Max', 25),    ...createManager()}const sportCoach = {    ...createEmployee('Bob', 23),    ...createSportCoach()}const sportCoachAndDriver = {    ...createEmployee('Robert', 27) ,    ...createDriver(),    ...createSportCoach() }

心愿你当初曾经能够了解继承组合之间的区别,一般来说,继承能够用于is-a关系,组合能够用于has-a

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

最初我想强调的是:继承组合都是很好实现,然而你应该正确的应用他们。一些场景组合可能更适合,反之亦然。

当然,咱们能够将继承组合联合在一起,比方咱们有is-a关系,但想增加不同的值或办法:咱们能够创立一些基类,为实例提供所有通用性能,而后应用组合来增加其余特定性能。

欢送关注“混沌前端”公众号