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