摘要:函數(shù)通常是面向?qū)ο缶幊田L(fēng)格,具有副作用。因?yàn)樵诤瘮?shù)式編程中,很有可能這些引用指向的并不是同一個(gè)對(duì)象。記住,函數(shù)并不意味著函數(shù)式編程。函數(shù)可以用函數(shù)式編程風(fēng)格編寫,避免副作用并不修改參數(shù),但這并不保證。
軟件構(gòu)建系列
原文鏈接:Functional Mixins
譯者注:在編程中,mixin 類似于一個(gè)固有名詞,可以理解為混合或混入,通常不進(jìn)行直譯,本文也是同樣。這是“軟件構(gòu)建”系列教程的一部分,該系列主要從 JavaScript ES6+ 中學(xué)習(xí)函數(shù)式編程,以及軟件構(gòu)建技術(shù)。敬請(qǐng)關(guān)注。
上一篇 | 第一篇
Mixin 函數(shù) 是指能夠給對(duì)象添加屬性或行為,并可以通過(guò)管道連接在一起的組合工廠函數(shù),就如同流水線上的工人。Mixin 函數(shù)不依賴或要求一個(gè)基礎(chǔ)工廠或構(gòu)造函數(shù):簡(jiǎn)單地將任意一個(gè)對(duì)象傳入一個(gè) mixin,就會(huì)得到一個(gè)增強(qiáng)之后的對(duì)象。
Mixin 函數(shù)的特點(diǎn):
數(shù)據(jù)封裝
繼承私有狀態(tài)
多繼承
覆蓋重復(fù)屬性
無(wú)需基礎(chǔ)類
動(dòng)機(jī)現(xiàn)代軟件開發(fā)的核心就是組合:我們將一個(gè)龐大復(fù)雜的問(wèn)題,分解成更小,更簡(jiǎn)單的問(wèn)題,最終將這些問(wèn)題的解決辦法組合起來(lái)就變成了一個(gè)應(yīng)用程序。
組合的最小單位就是以下兩者之一:
函數(shù)
數(shù)據(jù)結(jié)構(gòu)
他們的組合就定義了應(yīng)用的結(jié)構(gòu)。
通常,組合對(duì)象由類繼承實(shí)現(xiàn),其中子類從父類繼承其大部分功能,并擴(kuò)展或覆蓋部分。這種方法導(dǎo)致了 is-a 問(wèn)題,比如:管理員是一名員工,這引發(fā)了許多設(shè)計(jì)問(wèn)題:
高耦合:由于子類的實(shí)現(xiàn)依賴于父類,所以類繼承是面向?qū)ο笤O(shè)計(jì)中最緊密的耦合。
脆弱的子類:由于高耦合,對(duì)父類的修改可能會(huì)破壞子類。軟件作者可能在不知情的情況下破壞了第三方管理的代碼。
層次不靈活:根據(jù)單一祖先分類,隨著長(zhǎng)時(shí)間的演變,最終所有的類都將不適用于新用例。
重復(fù)問(wèn)題:由于層次不靈活,新用例通常是通過(guò)重復(fù)而不是擴(kuò)展來(lái)實(shí)現(xiàn)的,這導(dǎo)致不同的類有著相似的類結(jié)構(gòu)。而一旦重復(fù)創(chuàng)建,在創(chuàng)建其子類時(shí),該繼承自哪個(gè)類以及為什么繼承于這個(gè)類就不清晰了。
大猩猩和香蕉問(wèn)題:“...面向?qū)ο笳Z(yǔ)言的問(wèn)題是他們會(huì)獲得所有與之相關(guān)的隱含環(huán)境。比如你想要一個(gè)香蕉,但你得到的會(huì)是一只拿著香蕉的大猩猩,以及一整片叢林。” - Joe Armstrong(Coders at Work)
假設(shè)管理員是一名員工,你如何處理聘請(qǐng)外部顧問(wèn)暫時(shí)行使管理員職務(wù)的情況?(譯者:木知啊~)如果你事先知道所有的需求,類繼承可能有效,但我從沒有看到過(guò)這種情況。隨著不斷地使用,新問(wèn)題和更有效的流程將會(huì)被發(fā)現(xiàn),應(yīng)用程序和需求不可避免地隨著時(shí)間的推移而發(fā)展和演變。
Mixin 提供了更靈活的方法。
什么是 Mixin?“組合優(yōu)于繼承。” - 設(shè)計(jì)模式:可重用面向?qū)ο筌浖脑?/p>
Mixin 是對(duì)象組合的一種,它將部分特性混入復(fù)合對(duì)象中,使得這些屬性成為復(fù)合對(duì)象的屬性。
面向?qū)ο缶幊讨械?"mixin" 一詞來(lái)源于冰激凌店。不同于將不同口味的冰激凌預(yù)先混合,每個(gè)顧客可以自由混合各種口味的冰激凌,從而創(chuàng)造出屬于自己的冰激凌口味。
對(duì)象 mixin 與之類似:從一個(gè)空對(duì)象開始,然后一步步擴(kuò)展它。由于 JavaScript 支持動(dòng)態(tài)對(duì)象擴(kuò)展,所以在 JavaScript 中使用對(duì)象 mixin 是非常簡(jiǎn)單的。它也是 JavaScript 中最常見的繼承形式,來(lái)看一個(gè)例子:
const chocolate = { hasChocolate: () => true }; const caramelSwirl = { hasCaramelSwirl: () => true }; const pecans = { hasPecans: () => true }; const iceCream = Object.assign({}, chocolate, caramelSwirl, pecans); /* // 支持對(duì)象擴(kuò)展符的話也可以寫成這樣... const iceCream = {...chocolate, ...caramelSwirl, ...pecans}; */ console.log(` hasChocolate: ${ iceCream.hasChocolate() } hasCaramelSwirl: ${ iceCream.hasCaramelSwirl() } hasPecans: ${ iceCream.hasPecans() } `); /* 輸出 hasChocolate: true hasCaramelSwirl: true hasPecans: true */什么是函數(shù)繼承?
函數(shù)繼承是指通過(guò)函數(shù)來(lái)增強(qiáng)對(duì)象實(shí)例實(shí)現(xiàn)特性繼承的過(guò)程。該函數(shù)建立一個(gè)閉包使得部分?jǐn)?shù)據(jù)是私有的,并通過(guò)動(dòng)態(tài)對(duì)象擴(kuò)展使得對(duì)象實(shí)例擁有新的屬性和方法。
來(lái)看一下這個(gè)詞的創(chuàng)造者 Douglas Crockford 所給出的例子。
// 父類 function base(spec) { var that = {}; // Create an empty object that.name = spec.name; // Add it a "name" property return that; // Return the object } // 子類 function child(spec) { // 調(diào)用父類構(gòu)造函數(shù) var that = base(spec); that.sayHello = function() { // Augment that object return "Hello, I"m " + that.name; }; return that; // Return it } // Usage var result = child({ name: "a functional object" }); console.log(result.sayHello()); // "Hello, I"m a functional object"
由于 child() 同 base() 緊密耦合在一起,當(dāng)你想添加 grandchild(), greatGrandchild() 等時(shí),你將面對(duì)類繼承中許多常見的問(wèn)題。
什么是 Mixin 函數(shù)?Mixin 函數(shù)是一系列將新的屬性或行為混入特定對(duì)象的組合函數(shù)。它不依賴或需要一個(gè)基礎(chǔ)工廠方法或構(gòu)造器,只需將任意對(duì)象傳入一個(gè) mixin 方法,它就會(huì)被擴(kuò)展。
來(lái)看下面的例子。
const flying = o => { let isFlying = false; return Object.assign({}, o, { fly () { isFlying = true; return this; }, isFlying: () => isFlying, land () { isFlying = false; return this; } }); }; const bird = flying({}); console.log( bird.isFlying() ); // false console.log( bird.fly().isFlying() ); // true
這里需要注意,當(dāng)調(diào)用 flying() 時(shí)需要傳遞一個(gè)被擴(kuò)展的對(duì)象。Mixin 函數(shù)被設(shè)計(jì)用來(lái)實(shí)現(xiàn)函數(shù)組合,繼續(xù)看下去。
const quacking = quack => o => Object.assign({}, o, { quack: () => quack }); const quacker = quacking("Quack!")({}); console.log( quacker.quack() ); // "Quack!"組合 Mixin 函數(shù)
通過(guò)簡(jiǎn)單的函數(shù)組合就可以將 mixin 函數(shù)組合起來(lái)。
const createDuck = quack => quacking(quack)(flying({})); const duck = createDuck("Quack!"); console.log(duck.fly().quack());
但是,這看上去有點(diǎn)丑陋,調(diào)試或重新排列組合順序也有點(diǎn)困難。
當(dāng)然,這只是標(biāo)準(zhǔn)的函數(shù)組合,而我們可以通過(guò)一些好的辦法來(lái)將它們組合起來(lái),比如 compose() 或 pipe()。如果,使用 pipe() 就需反轉(zhuǎn)函數(shù)的調(diào)用順序,才能保持相同的執(zhí)行順序。當(dāng)屬性沖突時(shí),最后的屬性生效。
const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x); // OR... // import pipe from `lodash/fp/flow`; const createDuck = quack => pipe( flying, quacking(quack) )({}); const duck = createDuck("Quack!"); console.log(duck.fly().quack());Mixin 函數(shù)的使用場(chǎng)景
你應(yīng)當(dāng)總是使用最簡(jiǎn)單的抽象來(lái)解決問(wèn)題。從純函數(shù)開始。如果需要一個(gè)持久化狀態(tài)的對(duì)象,就試試工廠方法。如果你需要構(gòu)建更復(fù)雜的對(duì)象,那就試試 Mixin 函數(shù)。
以下是一些使用 Mixin 函數(shù)很棒的例子:
應(yīng)用狀態(tài)管理,比如,Redux
某些橫向服務(wù),比如,集中日志處理
組件生命周期函數(shù)
功能可組合的數(shù)據(jù)類型,比如,JavaScript Array 類實(shí)現(xiàn)了 Semigroup, Functor, Foldable
一些代數(shù)結(jié)構(gòu)可以根據(jù)其他代數(shù)結(jié)構(gòu)得出,這意味著新的數(shù)據(jù)類型可以通過(guò)某些推導(dǎo)組合而成,而不需要定制。
注意事項(xiàng)大部分問(wèn)題都可以使用純函數(shù)優(yōu)雅地解決。然而,mixin 函數(shù)同類繼承一樣,會(huì)造成一些問(wèn)題。事實(shí)上,使用 mixin 函數(shù)能夠完全復(fù)制類繼承的優(yōu)缺點(diǎn)。
你應(yīng)當(dāng)遵循以下的建議來(lái)避免這些問(wèn)題。
使用最簡(jiǎn)單的實(shí)現(xiàn)。從左邊開始,根據(jù)需要移到右邊。純函數(shù) > 工廠方法 > mixin 函數(shù) > 類繼承
避免創(chuàng)建對(duì)象,mixin,或數(shù)據(jù)類型之間的 is-a 關(guān)系
避免 mixins 之間的隱含依賴關(guān)系,mixin 函數(shù)應(yīng)當(dāng)是獨(dú)立的
mixin 函數(shù)并不意味著函數(shù)式編程
類繼承在 JavaScript 中,類繼承在極少情況下(也許永遠(yuǎn)不)會(huì)是最佳方案,但這通常是一些不由你控制的庫(kù)或框架。在這種場(chǎng)景下,類有時(shí)是實(shí)用的。
無(wú)需擴(kuò)展你自己的類(不需要你建立多層次的類結(jié)構(gòu))
無(wú)需使用 new 關(guān)鍵字,也就是說(shuō),框架會(huì)替你實(shí)例化
Angular 2+ 和 React 滿足這些需求,所以你無(wú)需擴(kuò)展你自己的類,而是放心地使用它們的類。在 React 中,你可以不使用類,不過(guò)這樣你的組件將不會(huì)獲得 React 的優(yōu)化,并且你的組件也會(huì)同文檔中的例子不同。但無(wú)論如何,使用函數(shù)構(gòu)建 React 組件總是你的首選。
性能在一些瀏覽器中,類會(huì)獲得 JavaScript 引擎的優(yōu)化,其他的則無(wú)法直接使用。在幾乎所有情況下,這些優(yōu)化都不會(huì)對(duì)程序產(chǎn)生決定性的影響。事實(shí)上,在接下去的幾年中,你都無(wú)需關(guān)心類在性能上的不同。無(wú)論你如何構(gòu)建對(duì)象,對(duì)象創(chuàng)建和屬性訪問(wèn)總是非常快的(每秒百萬(wàn)次)。
也就是說(shuō),類似 RxJS,Lodash 等公共庫(kù)的作者應(yīng)該研究使用 class 創(chuàng)建對(duì)象實(shí)例可能的性能優(yōu)勢(shì)。除非你能夠證明通過(guò)類能夠解決性能瓶頸,否則,你就應(yīng)當(dāng)使你的代碼保持干凈、靈活,而不必?fù)?dān)心性能。
隱式依賴你可能打算創(chuàng)建一些計(jì)劃用于一同工作的 mixin 函數(shù)。試想一下,你想要為你的應(yīng)用添加一個(gè)配置管理器,當(dāng)你訪問(wèn)不存在的配置屬性時(shí),它會(huì)提示警告,像這樣:
// log 模塊 const withLogging = logger => o => Object.assign({}, o, { log (text) { logger(text) } }); // 確認(rèn)配置項(xiàng)存在模塊,同 log 模塊無(wú)關(guān),這里只是確保 log 存在 const withConfig = config => (o = { log: (text = "") => console.log(text) }) => Object.assign({}, o, { get (key) { return config[key] == undefined ? // vvv 隱式依賴! vvv this.log(`Missing config key: ${ key }`) : // ^^^ 隱式依賴! ^^^ config[key] ; } }); // 模塊封裝 const createConfig = ({ initialConfig, logger }) => pipe( withLogging(logger), withConfig(initialConfig) )({}) ; // 調(diào)用 const initialConfig = { host: "localhost" }; const logger = console.log.bind(console); const config = createConfig({initialConfig, logger}); console.log(config.get("host")); // "localhost" config.get("notThere"); // "Missing config key: notThere"
也可以是這樣,
// 引入 log 模塊 import withLogging from "./with-logging"; const addConfig = config => o => Object.assign({}, o, { get (key) { return config[key] == undefined ? this.log(`Missing config key: ${ key }`) : config[key] ; } }); const withConfig = ({ initialConfig, logger }) => o => pipe( // vvv 明確的依賴! vvv withLogging(logger), // ^^^ 明確的依賴! ^^^ addConfig(initialConfig) )(o) ; // 工廠方法 const createConfig = ({ initialConfig, logger }) => withConfig({ initialConfig, logger })({}) ; // 另一模塊 const initialConfig = { host: "localhost" }; const logger = console.log.bind(console); const config = createConfig({initialConfig, logger}); console.log(config.get("host")); // "localhost" config.get("notThere"); // "Missing config key: notThere"
選擇隱式還是顯式取決于很多因素。Mixin 函數(shù)作用的數(shù)據(jù)類型必須是有效的,這就需要 API 文檔中的函數(shù)簽名非常清晰。
這就是隱式依賴版本中為 o 添加默認(rèn)值的原因。由于 JavaScript 缺少類型注釋功能,但我們可以通過(guò)默認(rèn)值來(lái)代替它。
const withConfig = config => (o = { log: (text = "") => console.log(text) }) => Object.assign({}, o, { // ...
如果你使用 TypeScript 或 Flow,最好為你的對(duì)象參數(shù)定義一個(gè)明確的接口。
Mixin 函數(shù)與函數(shù)式編程Mixin 函數(shù)并不像函數(shù)式編程那樣純。Mixin 函數(shù)通常是面向?qū)ο缶幊田L(fēng)格,具有副作用。許多 Mixin 函數(shù)會(huì)改變傳入的參數(shù)對(duì)象。注意!
出于同樣的原因,一些開發(fā)者更喜歡函數(shù)式編程風(fēng)格,不修改傳入的對(duì)象。在編寫 mixin 時(shí),你應(yīng)當(dāng)適當(dāng)?shù)厥褂眠@兩種編碼風(fēng)格。
這意味著,如果你要返回對(duì)象的實(shí)例,則始終返回 this,而不是閉包中對(duì)象實(shí)例的引用。因?yàn)樵诤瘮?shù)式編程中,很有可能這些引用指向的并不是同一個(gè)對(duì)象。另外,總是使用 Object.assign() 或 {...object, ...spread} 語(yǔ)法進(jìn)行復(fù)制。但需要注意的是,非枚舉的屬性將不會(huì)存在于最終的對(duì)象上。
const a = Object.defineProperty({}, "a", { enumerable: false, value: "a" }); const b = { b: "b" }; console.log({...a, ...b}); // { b: "b" }
出于同樣的原因,如果你使用的 mixin 函數(shù)不是自己構(gòu)建的,就不要認(rèn)為它就是純的。假設(shè)基礎(chǔ)對(duì)象會(huì)被改變,假設(shè)它可能會(huì)產(chǎn)生副作用,不保證參數(shù)不會(huì)改變,即由 mixin 函數(shù)組合而成的記錄工廠通常是不安全的。
結(jié)論Mixin 函數(shù)是可組合的工廠方法,它能夠?yàn)閷?duì)象添加屬性和行為,就如同裝配線上的站。它是將多個(gè)來(lái)源的功能(has-a, uses-a, can-do)組合成行為的好方法,而不是從一個(gè)類上繼承所有功能(is-a)。
記住,“mixin 函數(shù)” 并不意味著“函數(shù)式編程”。Mixin 函數(shù)可以用函數(shù)式編程風(fēng)格編寫,避免副作用并不修改參數(shù),但這并不保證。第三方 mixin 可能存在副作用和不確定性。
不同于對(duì)象 mixin,mixin 函數(shù)支持正真的私有數(shù)據(jù)(封裝),包括繼承私有數(shù)據(jù)的能力。
不同于單繼承,mixin 函數(shù)還支持繼承多個(gè)祖先的能力,類似于類裝飾器或多繼承。
不同于 C++ 中的多繼承,JavaScript 中很少出現(xiàn)屬性沖突問(wèn)題,當(dāng)屬性沖突發(fā)生時(shí),總是最后添加的 mixin 有效。
不同于類裝飾器或多繼承,不需要基類
總是從最簡(jiǎn)單的實(shí)現(xiàn)方式開始,只根據(jù)需要使用更復(fù)雜的實(shí)現(xiàn)方式:
純函數(shù) > 工廠方法 > mixin 函數(shù) > 類繼承
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/83670.html
摘要:可以說(shuō),相比繼承而已,更喜歡這種組合的方式。需要指出的是,是可以包含在其他的中的程序會(huì)在控制臺(tái)打印出。包含多個(gè)我們的要包裹在數(shù)組當(dāng)中,提醒了我們可以在組件中包含多個(gè)注意事項(xiàng)這里有幾件事需要引起我們的注意,當(dāng)我們使用的時(shí)候。 update: Mixin 只適用于 ES5。如果你的項(xiàng)目里面用的是 ES6 ,可以采用高階組件來(lái)實(shí)現(xiàn) Mixin 的功能。 我使用 React.js 構(gòu)建大型項(xiàng)目...
摘要:使用構(gòu)造函數(shù)的原型繼承相比使用原型的原型繼承更加復(fù)雜,我們先看看使用原型的原型繼承上面的代碼很容易理解。相反的,使用構(gòu)造函數(shù)的原型繼承像下面這樣當(dāng)然,構(gòu)造函數(shù)的方式更簡(jiǎn)單。 五天之前我寫了一個(gè)關(guān)于ES6標(biāo)準(zhǔn)中Class的文章。在里面我介紹了如何用現(xiàn)有的Javascript來(lái)模擬類并且介紹了ES6中類的用法,其實(shí)它只是一個(gè)語(yǔ)法糖。感謝Om Shakar以及Javascript Room中...
摘要:譯立即執(zhí)行函數(shù)表達(dá)式處理支持瀏覽器環(huán)境微信小程序。學(xué)習(xí)整體架構(gòu),利于打造屬于自己的函數(shù)式編程類庫(kù)。下一篇文章可能是學(xué)習(xí)的源碼整體架構(gòu)。也可以加微信,注明來(lái)源,拉您進(jìn)前端視野交流群。 前言 上一篇文章寫了jQuery整體架構(gòu),學(xué)習(xí) jQuery 源碼整體架構(gòu),打造屬于自己的 js 類庫(kù) 雖然看過(guò)挺多underscore.js分析類的文章,但總感覺少點(diǎn)什么。這也許就是紙上得來(lái)終覺淺,絕知此...
摘要:在吸取了的一些特性基礎(chǔ)上,有了大幅改進(jìn),也就是現(xiàn)在的。嵌套極大程度上降低了選擇器名稱和屬性的重復(fù)書寫。選擇器嵌套選擇器嵌套是指從一個(gè)選擇器中嵌套子選擇器,來(lái)實(shí)現(xiàn)選擇器的繼承關(guān)系。也已經(jīng)成為的一個(gè)標(biāo)配組件。 SASS是Syntactically Awesome Stylesheete Sass的縮寫,它是css的一個(gè)開發(fā)工具,提供了很多便利和簡(jiǎn)單的語(yǔ)法,讓css看起來(lái)更像是一門...
摘要:基于的語(yǔ)言比特幣開發(fā)教程用機(jī)器人接受和發(fā)送比特幣在上一篇教程中我們創(chuàng)建了自動(dòng)回復(fù)消息的機(jī)器人當(dāng)用戶發(fā)送消息時(shí),機(jī)器人會(huì)自動(dòng)回復(fù)同一條消息按本篇教程后學(xué)習(xí)后完成后,你的機(jī)器人將會(huì)接受用戶發(fā)送過(guò)來(lái)的加密貨幣,然后立即轉(zhuǎn)回用戶。 基于Mixin Network的Go語(yǔ)言比特幣開發(fā)教程 : 用 Mixin Messenger 機(jī)器人接受和發(fā)送比特幣 showImg(https://segmen...
閱讀 2907·2021-10-19 10:09
閱讀 3126·2021-10-09 09:41
閱讀 3371·2021-09-26 09:47
閱讀 2687·2019-08-30 15:56
閱讀 590·2019-08-29 17:04
閱讀 979·2019-08-26 11:58
閱讀 2505·2019-08-26 11:51
閱讀 3353·2019-08-26 11:29