乐趣区

关于javascript:JavaScript中的继承和组合

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

这是一篇翻译文章,作者是 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 关系,但想增加不同的值或办法:咱们能够创立一些基类,为实例提供所有通用性能,而后应用组合来增加其余特定性能。

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

退出移动版