继承与组合都是面向对象中代码复用的形式,理解各自有什么特点,能够让咱们写出更简洁的代码,设计出更好的代码架构。
这是一篇翻译文章,作者是 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) ;
咱们创立了应用 Engine
和Transmission
创立了 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
关系,但想增加不同的值或办法:咱们能够创立一些基类,为实例提供所有通用性能,而后应用组合来增加其余特定性能。
欢送关注“混沌前端”公众号