如何刪掉編程中的?Switch?語句
多重方法是一種有趣的方式,可以幫你擺脫令人討厭的 switch。而且,這也有助于提升代碼的可讀性。所以,在決定繼續(xù)堅持使用 switch 之前,一定要先試一試。
本文最初發(fā)布于 Bits and Pieces。
很多開發(fā)者都討厭switch
語句,包括我。并不是因為這個語句沒用,也不是因為它太難了。
理解switch
語句的工作原理非常簡單,問題是當你真的遇到它時,就必須停下手頭的一切工作,集中精力閱讀它,以確保不會遺漏任何東西,比如,缺少break
語句可能會導致一些意想不到的行為,或者一個case
中大約有 20 行代碼。
關鍵是,原諒我使用一個花哨的術語:理解switch
語句(在現(xiàn)實世界中)所需要的認知負荷相當重。我相信,作為開發(fā)人員,我們的目標是編寫方便人類閱讀的代碼。在這方面,這個語句提供不了什么幫助。
但是,我寫這篇文章不是為了對它進行抨擊,我是要向你(之前也包括我)展示三個關于如何避免使用switch
語句的示例,讓我們來看一種函數(shù)式編程技術:多重方法。
什么是多重方法?
我第一次聽到這個詞,還是在播客“20 MinJS”中采訪 Yehonathan Sharvit 時。當時的采訪是關于他即將由 Manning 出版的著作《面向數(shù)據(jù)的編程》。
他提出這一概念是為了從功能上取代繼承,這無疑是可行的。在這個過程中,他展示了switch
語句是如何被取代的。因此,讓我們暫時把 OOP 放在一邊,只關注第二部分:消除代碼中丑陋的switch
。
什么是多重方法?它只是一個能夠根據(jù)接收到的參數(shù)選擇最佳實現(xiàn)的函數(shù)。換句話說,想象一下,如果你把丑陋的switch
語句放在函數(shù)中,然后對所有人隱藏實現(xiàn)。
唯一的區(qū)別是,你的解決方案只適用于一個函數(shù)。今天我們將討論如何在運行中生成多個多重方法。
多重方法是什么樣子?
當然,每種語言都有自己的變體,但我今天主要講 JavaScript。
在這種語言中,多重方法的使用方法如下:
//我們將使用的數(shù)據(jù) const myDog = { type: "dog", name:"Robert" } const myCat = { type: "cat", name: "Steffan" } //自定義函數(shù)實現(xiàn) function greetDogs (dog) { console.log("Hello dear Dog, how are you today", dog.name, "?") } function greetCats(cat) { console.log("What's up", cat.name, "?") } //定義我們的多重方法 let greeter = null greeter = multi( animal => animal.type, method("dog", greetDogs), method("cat", greetCats) )(greeter) // 調用多重方法 greeter(myDog) greeter(myCat)
這個例子做了很多事,讓我來說明下:
我定義了 2 個對象
myCat
和myDog
,我將把它們作為參數(shù),多重方法將根據(jù)它們確定自己的行為。我定義了 2 個自定義函數(shù)
greetDogs
和greetCats
,它們的實現(xiàn)稍有不同。它們將代表switch
中每個case
語句里的代碼。然后我調用一些函數(shù),尤其是
multi
和method
,來定義多重方法greeter
。multi
函數(shù)接收 3 個屬性:一個分配器(dispatcher),我們將用它返回的值來確定要執(zhí)行的邏輯片段;還有兩個方法,分別代表switch
的一個case
語句。請注意,每次調用method
時,要首先指定觸發(fā)第二個參數(shù)的值(這是實際的邏輯所在)。最后,我使用同一個函數(shù)(我的多重方法)來執(zhí)行兩個不同的邏輯片段,而不需要在任何地方使用
switch
或if
語句。
多重方法有什么好處?
當然,我們在這里沒有施展任何類型的魔法,我們只是重寫了決策邏輯的表達方式,類似下面這樣的switch
語句:
switch(animal.type) { case "dog": greetDogs(animal); break; case "cat": greetCats(animal); break; }
那么,如果我們可以直接這樣做,為什么還要大費周章地使用多重方法呢?問題的關鍵是可讀性。
switch
語句非常開放,顯示了我們的決策邏輯的實現(xiàn)。換句話說,這個語句是命令式的。它向你展示了決策樹的內(nèi)部運作情況,這意味著閱讀代碼的人將不得不在頭腦中解析代碼。因此,我們又回到了認知負荷的概念。這使得開發(fā)者要閱讀并在頭腦中解析代碼。
你要知道,大多數(shù)開發(fā)人員在遇到像上面這樣的switch
時,不會有什么反應。但是,這也不是一個實際的例子。通常情況下,case
語句包含的代碼更多,也更難閱讀。
而多重方法隱藏了決策邏輯的內(nèi)部結構,你所知道的只是你對它做了設置,它將以某種方式工作。你更關心的是功能而不是實際的實現(xiàn)。這被稱為“聲明式編程”,有助于提高代碼的可讀性,同時降低開發(fā)人員的認知負擔。這是因為它在邏輯上增加了一層抽象,為我們提供了更接近人類語言的表達工具。
如果這還不能說服你,還有一個優(yōu)點:可擴展性。
如果你需要在switch
中添加另一個選項,就必須回到代碼中修改同一個switch
,如果你,比如說,碰巧忘記添加break
語句,就有可能造成問題,就像下面這樣:
switch(animal.type) { case "rabbit": greetRabbits(animal); case "dog": greetDogs(animal); break; case "cat": greetCats(animal); break; }
還是個非常簡單的例子,但如果是真實世界中一段更長的代碼,那么這種情況出現(xiàn)的幾率就更大了。
以防你對這種行為不熟悉,請讓我做個說明。第一個case
中缺失break
,會導致在動物類型為“rabbit”時也執(zhí)行第二個case
下的邏輯。
然而,有了多重方法,我們就可以不斷地根據(jù)需要對它進行擴展:
let extendedGreeter = multi( animal => animal.type, method("parrot", sayHiParrot) )(greeter)
現(xiàn)在,這個新方法extendedGreeter
對“dog”、“cat“、”parrot“就都有效了,而我們不必再回去修改已有的代碼。
這是一個很大的好處,因為我們都知道,每次我們觸碰可以正常工作的代碼時,都有一點可能引入 Bug。在這里,我們把可能性降低到 0。
實現(xiàn)一個多重方法庫
首先,你要知道,已經(jīng)有一些庫在處理這個問題了,其中一個例子是@arrows/multimethod。
盡管如此,對這些實現(xiàn)進行逆向工程總是很有趣,所以讓我們看一看如何實現(xiàn)一個基本的多重方法庫,以適應到目前為止所展示的例子。
理解這個問題的關鍵是,我們需要一個分配器函數(shù)來給提供一個實際的值,我們將用它作為判斷執(zhí)行哪個方法的鍵。而且,我們不能對switch
語句進行硬編碼,因為選項的數(shù)量是不固定的。
不能光說不練,下面是實現(xiàn):
function method(value, fn) { return {value, fn} } function multi(dispatcher, ...methods) { return (originalFn) => { return (elem) => { let key = dispatcher(elem) let method = methods.find( m => m.value === key) if(!method) { if(originalFn) { return originalFn(elem) } else { throw new Error("No sure what to do with this option!") } } return method.fn(elem) } } }
method
函數(shù)只是把鍵和實際的邏輯耦合在一起,沒有別的。multi
函數(shù)中的代碼才有趣,它返回一個匿名函數(shù),以原始函數(shù)為參數(shù)并返回一個新函數(shù),后者根據(jù)分配器代碼(我們的第一個參數(shù))返回的值執(zhí)行不同的東西。
讓我們逐行看下:
首先,調用第 8 行的函數(shù)時提供一個屬性(比方說
myDog
)。第 9 行的分配器邏輯會獲取
myDog
并返回其類型,即“dog
”。然后在第 10 行,我們找到第一個與該類型匹配的方法。
如果沒有方法匹配,但我們有一個有效的“
originalFn
”(也就是說,我們正在擴展一個原始的多重方法),我們會讓它來處理這種情況。否則,我們將拋出一個異常,因為我們對此無能為力。然而,如果找到了匹配的方法,就在第 18 行執(zhí)行它,并將原始屬性“
myDog
”傳遞給它。
就是這樣。沒那么復雜,對嗎?當然,如果你想提供“默認”情況處理而不是拋出一個異常,或者你想處理多屬性決策(比如根據(jù)屬性type
和name
決定邏輯,而不是只根據(jù)第一個屬性),就得編寫更多的代碼了。
不過,還是那句話,如果你打算使用多重方法,建議你使用一個現(xiàn)有的庫,而不是自己去實現(xiàn)。
多重方法是一種有趣的方式,可以幫你擺脫令人討厭的switch
。而且,這也有助于提升代碼的可讀性。所以,既然你已經(jīng)了解了多重方法,那么在決定繼續(xù)堅持使用switch
之前,一定要先試一試。
到此這篇關于如何刪掉編程中的 Switch 語句的文章就介紹到這了,更多相關Switch 語句刪掉內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
swift4 使用DrawerController實現(xiàn)側滑菜單功能的示例代碼
這篇文章主要介紹了swift4 使用DrawerController實現(xiàn)側滑功能的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-06-06Swift?Sequence?Collection使用示例學習
這篇文章主要為大家介紹了Swift?Sequence?Collection使用示例學習,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-07-07