JavaScript中的繼承和組合

繼承與組合都是面向?qū)ο笾写a復用的方式,了解各自有什么特點,可以讓我們寫出更簡潔的代碼沮尿,設計出更好的代碼架構穴豫。

這是一篇翻譯文章,作者是serhiirubets

“我應該怎么使用繼承和組合”這是一個常見的問題围辙,不僅是JavaScript相關,但是本篇我們只討論JavaScript相關的內(nèi)容和示例。

如果你不知道什么是組合繼承赎败,我強烈推薦你去查看相關內(nèi)容,因為本文的主要講的就是怎么使用和如何選擇它們蠢甲,但是為了確定我們在一個頻道僵刮,讓我們先了解一下組合和繼承吧。

繼承是面向?qū)ο缶幊毯诵母拍钪火信#梢詭椭覀儽苊獯a重復搞糕。主要的思想是我們可以創(chuàng)建一個包含邏輯的基類,可以被子類重用曼追。我們來看個示例:

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

我們創(chuàng)建了一個基類“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) ;

我們創(chuàng)建了使用EngineTransmission創(chuàng)建了Car钥屈,我們不能說Engine是一個Car悟民,但是可以說Car包含Engine。希望上面的例子可以幫助你理解什么是繼承篷就,什么是組合射亏。

我們再來看兩個不同的示例,對比一下使用類的方法實現(xiàn)和函數(shù)方法實現(xiàn)有什么區(qū)別竭业。

假設我們正在使用文件系統(tǒng)智润,想實現(xiàn)讀取、寫入和刪除的功能未辆。我們可以創(chuàng)建一個類:

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() {}
}

現(xiàn)在每個用戶可以使用其需要的權限拙友,但是還有一個問題为狸,如果我們需要給一些人同時分配讀取和寫入權限,應該怎么辦遗契?同時分配讀取和刪除權限怎么辦辐棒?使用當前的實現(xiàn),我們做不到牍蜂,應該怎么解決?

第一個想到的方案可能是:為讀取和寫入創(chuàng)建一個類漾根,為讀取和刪除創(chuàng)建一個類。

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

按照這種做法鲫竞,我們可能還需要以下類: FileReader, FileWriter, FileRemove, FileReaderAndWriter, FileReaderAndRemover辐怕。

這不是一個好的實現(xiàn)方式:第一,我們可能不僅有3種贡茅,而是10秘蛇、20種方法其做,還需要在他們之間有大量的組合顶考。第二是我們的類中存在重復的邏輯,F(xiàn)ileReader類包含讀取方法妖泄,F(xiàn)ileReaderAndWriter也包含同樣的代碼驹沿。

這不是一個很好的解決方案,還有其他的實現(xiàn)方法嗎蹈胡?多重繼承渊季?JavaScript中沒有這個特性朋蔫,而且也不是很好的方案:A類繼承了B類,B類可能繼承了其他類...却汉,這樣的設計會非逞蓖混亂,不是一個良好的代碼架構合砂。

怎么解決呢青扔?一個合理的方法是使用組合:我們把方法拆分為單獨的函數(shù)工廠。

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

上面的示例中翩伪,我們有兩個函數(shù)創(chuàng)建了可以讀取和寫入的對象∥⒉現(xiàn)在就可以在任何我們地方使用它們,也可以將它們進行組合:

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(),
    }
}

上面的例子中缘屹,我們創(chuàng)建了讀取和寫入服務凛剥,如果我們想組合不同的權限:讀取、寫入和刪除轻姿,我們可以很容易的做到:

function createReadAndWriteFileService (filename) {
    const file = new FileService(filename);
}
return {
    ...file,
    ...createReader(),
    ...createWriter()
}

const fileForReadAndWriter = createReadAndWriteFileService('test');
fileForReadAndWriter.read();
fileForReadAndWriter.write();

如果我們有5犁珠、10、20種方法互亮,我們可以按照我們想要的方式進行組合盲憎,不會有重復的代碼問題,也沒有令人困惑的代碼架構胳挎。

我們再來看一個使用函數(shù)的例子饼疙,假設我們有很多員工,有出租車司機慕爬、健身教練和司機:

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

看起來沒有問題窑眯,但是假設有一些員工白天當健身教練,晚上去跑出租医窿,我們應該怎么調(diào)整呢磅甩?

function createDriverAndSportCoach(name) {
    return {
        name,
        canSport: true,
        canDriver: true
    }
}

可以實現(xiàn),但是和第一個例子一樣姥卢,如果我們有多種類型混合卷要,就會產(chǎn)生大量重復的代碼。我們可以通過組合來進行重構:

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

現(xiàn)在我們可以根據(jù)需要組合所有工作類型独榴,沒有重復代碼僧叉,也更容易理解:

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() 
}

希望你現(xiàn)在已經(jīng)可以理解繼承組合之間的區(qū)別,一般來說棺榔,繼承可以用于is-a關系瓶堕,組合可以用于has-a

但在實踐中症歇,繼承有時候并不是一個好的解決方法:就像示例中郎笆,司機是員工(is-a關系)谭梗,經(jīng)理也是員工,如果我們需要把不同的部分進行混合宛蚓,組合確實比繼承更合適激捏。

最后我想強調(diào)的是:繼承組合都是很好實現(xiàn),但是你應該正確的使用他們凄吏。一些場景組合可能更合適缩幸,反之亦然。

當然竞思,我們可以將繼承組合結合在一起表谊,比如我們有is-a關系,但想添加不同的值或方法:我們可以創(chuàng)建一些基類盖喷,為實例提供所有通用功能爆办,然后使用組合來添加其他特定功能。

歡迎關注“混沌前端”公眾號

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末课梳,一起剝皮案震驚了整個濱河市距辆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌暮刃,老刑警劉巖跨算,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異椭懊,居然都是意外死亡诸蚕,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門氧猬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來背犯,“玉大人,你說我怎么就攤上這事盅抚∧海” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵妄均,是天一觀的道長柱锹。 經(jīng)常有香客問我,道長丰包,這世上最難降的妖魔是什么禁熏? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮烫沙,結果婚禮上匹层,老公的妹妹穿的比我還像新娘隙笆。我一直安慰自己锌蓄,他們只是感情好升筏,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著瘸爽,像睡著了一般您访。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上剪决,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天灵汪,我揣著相機與錄音,去河邊找鬼柑潦。 笑死享言,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的渗鬼。 我是一名探鬼主播览露,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼譬胎!你這毒婦竟也來了差牛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤堰乔,失蹤者是張志新(化名)和其女友劉穎偏化,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體镐侯,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡侦讨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了苟翻。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搭伤。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖袜瞬,靈堂內(nèi)的尸體忽然破棺而出怜俐,到底是詐尸還是另有隱情,我是刑警寧澤邓尤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布拍鲤,位于F島的核電站,受9級特大地震影響汞扎,放射性物質(zhì)發(fā)生泄漏季稳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一澈魄、第九天 我趴在偏房一處隱蔽的房頂上張望景鼠。 院中可真熱鬧,春花似錦、人聲如沸铛漓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽浓恶。三九已至玫坛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間包晰,已是汗流浹背湿镀。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留伐憾,地道東北人勉痴。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像树肃,于是被迫代替她去往敵國和親蚀腿。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345