多重方法是一种有趣的方式,可以帮你摆脱令人讨厌的 switch。而且,这也有助于提升代码的可读性。所以,在决定继续坚持使用 switch 之前,一定要先试一试。
很多开发者都讨厌 switch 语句,包括我。并不是因为这个语句没用,也不是因为它太难了。
理解 switch 语句的工作原理非常简单,问题是当你真的遇到它时,就必须停下手头的一切工作,集中精力阅读它,以确保不会遗漏任何东西,比如,缺少 break 语句可能会导致一些意想不到的行为,或者一个 case 中大约有 20 行代码。
关键是,原谅我使用一个花哨的术语:理解 switch 语句(在现实世界中)所需要的认知负荷相当重。我相信,作为开发人员,我们的目标是编写方便人类阅读的代码。在这方面,这个语句提供不了什么帮助。
但是,我写这篇文章不是为了对它进行抨击,我是要向你( 之前也包括我 )展示三个关于如何避免使用 switch 语句的示例,让我们来看一种函数式编程技术:多重方法。
我第一次听到这个词,还是在播客“ 20 MinJS ”中采访 Yehonathan Sharvit 时。当时的采访是关于他即将由 Manning 出版的著作《 面向数据的编程 》。
他提出这一概念是为了从功能上取代继承,这无疑是可行的。在这个过程中,他展示了 switch 语句是如何被取代的。因此,让我们暂时把 OOP 放在一边,只关注第二部分:消除代码中丑陋的 switch 。
什么是多重方法?它只是一个能够根据接收到的参数选择最佳实现的函数。换句话说,想象一下,如果你把丑陋的 switch 语句放在函数中,然后对所有人隐藏实现。
唯一的区别是,你的解决方案只适用于一个函数。今天我们将讨论如何在运行中生成多个多重方法。
当然,每种语言都有自己的变体,但我今天主要讲 JavaScript。
在这种语言中,多重方法的使用方法如下:
//我们将使用的数据
const myDog = {
type: "dog",
name:"Robert"
}
const myCat = {
type: "cat",
name: "Steffan"
}
//自定义函数实现
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 ,我将把它们作为参数,多重方法将根据它们确定自己的行为。
我定义了 2 个自定义函数 greetDogs 和 greetCats ,它们的实现稍有不同。它们将代表 switch 中每个 case 语句里的代码。
然后我调用一些函数,尤其是 multi 和 method ,来定义多重方法 greeter 。 multi 函数接收 3 个属性:一个分配器(dispatcher),我们将用它返回的值来确定要执行的逻辑片段;还有两个方法,分别代表 switch 的一个 case 语句。请注意,每次调用 method 时,要首先指定触发第二个参数的值(这是实际的逻辑所在)。
最后,我使用同一个函数(我的多重方法)来执行两个不同的逻辑片段,而不需要在任何地方使用 switch 或 if 语句。
当然,我们在这里没有施展任何类型的魔法,我们只是重写了决策逻辑的表达方式,类似下面这样的 switch 语句:
switch(animal.type) {
case "dog":
greetDogs(animal);
break;
case "cat":
greetCats(animal);
break;
}
那么,如果我们可以直接这样做,为什么还要大费周章地使用多重方法呢?问题的关键是可读性。
switch 语句非常开放,显示了我们的决策逻辑的实现。换句话说,这个语句是命令式的。它向你展示了决策树的内部运作情况,这意味着阅读代码的人将不得不在头脑中解析代码。因此,我们又回到了认知负荷的概念。这使得开发者要阅读并在头脑中解析代码。
你要知道,大多数开发人员在遇到像上面这样的 switch 时,不会有什么反应。但是,这也不是一个实际的例子。通常情况下, case 语句包含的代码更多,也更难阅读。
而多重方法隐藏了决策逻辑的内部结构,你所知道的只是你对它做了设置,它将以某种方式工作。你更关心的是功能而不是实际的实现。这被称为“声明式编程”,有助于提高代码的可读性,同时降低开发人员的认知负担。这是因为它在逻辑上增加了一层抽象,为我们提供了更接近人类语言的表达工具。
如果这还不能说服你,还有一个优点:可扩展性。
如果你需要在 switch 中添加另一个选项,就必须回到代码中修改同一个 switch ,如果你,比如说,碰巧忘记添加 break 语句,就有可能造成问题,就像下面这样:
switch(animal.type) {
case "rabbit":
greetRabbits(animal);
case "dog":
greetDogs(animal);
break;
case "cat":
greetCats(animal);
break;
}
还是个非常简单的例子,但如果是真实世界中一段更长的代码,那么这种情况出现的几率就更大了。
以防你对这种行为不熟悉,请让我做个说明。第一个 case 中缺失 break ,会导致在动物类型为“rabbit”时也执行第二个 case 下的逻辑。
然而,有了多重方法,我们就可以不断地根据需要对它进行扩展:
let extendedGreeter = multi(
animal => animal.type,
method("parrot", sayHiParrot)
)(greeter)
现在,这个新方法 extendedGreeter 对“dog”、“cat“、”parrot“就都有效了,而我们不必再回去修改已有的代码。
这是一个很大的好处,因为我们都知道,每次我们触碰可以正常工作的代码时,都有一点可能引入 Bug。在这里,我们把可能性降低到 0。
首先,你要知道,已经有一些库在处理这个问题了,其中一个例子是 @arrows/multimethod 。
尽管如此,对这些实现进行逆向工程总是很有趣,所以让我们看一看如何实现一个基本的多重方法库,以适应到目前为止所展示的例子。
理解这个问题的关键是,我们需要一个分配器函数来给提供一个实际的值,我们将用它作为判断执行哪个方法的键。而且,我们不能对 switch 语句进行硬编码,因为选项的数量是不固定的。
不能光说不练,下面是实现:
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 函数只是把键和实际的逻辑耦合在一起,没有别的。 multi 函数中的代码才有趣,它返回一个匿名函数,以原始函数为参数并返回一个新函数,后者根据分配器代码(我们的第一个参数)返回的值执行不同的东西。
让我们逐行看下:
首先,调用第 8 行的函数时提供一个属性(比方说 myDog )。
第 9 行的分配器逻辑会获取 myDog 并返回其类型,即“ dog ”。
然后在第 10 行,我们找到第一个与该类型匹配的方法。
如果没有方法匹配,但我们有一个有效的“ originalFn ”(也就是说,我们正在扩展一个原始的多重方法),我们会让它来处理这种情况。否则,我们将抛出一个异常,因为我们对此无能为力。
然而,如果找到了匹配的方法,就在第 18 行执行它,并将原始属性“ myDog ”传递给它。
就是这样。没那么复杂,对吗?当然,如果你想提供“默认”情况处理而不是抛出一个异常,或者你想处理多属性决策(比如根据属性 type 和 name 决定逻辑,而不是只根据第一个属性),就得编写更多的代码了。
不过,还是那句话,如果你打算使用多重方法,建议你使用一个现有的库,而不是自己去实现。
多重方法是一种有趣的方式,可以帮你摆脱令人讨厌的 switch 。而且,这也有助于提升代码的可读性。所以,既然你已经了解了多重方法,那么在决定继续坚持使用 switch 之前,一定要先试一试。
查看英文原文:Drop the Switch Statement for this Functional Programming Technique
最近我正在跟我的团队伙伴讨论如何去处理这种需要根据不同的值去处理不同的情况的方法,通常对于这种情况下,人们喜欢使用switch语句或者使用很多if搭配else if条件。在本文中我将重点介绍第三种方式(我更为喜欢的方法),即使用对象进行快速地查找。
在JavaScript里面,一般我们想让程序在function里面执行到一半时,经过判断或者完成某种操作后停止执行后面的代码,会用return结束掉function。可以在while中间加入多个if()break作为断点,控制逻辑的流程。
相信我们程序员平时写代码用的最多的逻辑就是if语句了吧,可你知道他的原理是什么吗,其实这跟Boolean()这个方法有关,此方法为自动执行(在需要判定Boolean的语句中中自动执行)
前一阵子猛然想到一个问题,JS有分号自动补全(ASI)机制,在写JS时我是习惯性在语句后面加上分号,在块语句后面不加分号。那么如果没有加分号的话,在什么情况下会出现问题?在JS语句后到底是应该加分号还是不加分号?
在JavaScript中,创建对象的方式包括两种:对象字面量和使用new表达式。对象字面量是一种灵活方便的书写方式,例如:new表达式是配合构造函数使用的:
在 HTML 中,JavaScript 语句是由 web 浏览器执行的指令。JavaScript 语句可以用花括号({...})组合在代码块中。JavaScript 语句常常通过某个关键词来标识需要执行的 JavaScript 动作。
简单逻辑常用判断,if..else if...else... 中的条件体应该总是按照从最大概率到最小概率排列,以保证理论速度最快,switch/case 条件数量较大的话,就建议选用
除了if ... else之外,JavaScript还有一个称为switch语句的功能。 switch是一种条件语句,它将针对多种可能的情况评估表达式,并根据匹配的情况执行一个或多个代码块。 switch语句与包含许多其他if块的条件语句密切相关
JavaScript return 语句,表示从被调函数返回到主调函数继续执行,返回时可附带一个返回值,由return后面的参数指定。return通常是必要的,因为函数调用的时候计算结果通常是通过返回值带出的。
表达式是由运算符构成,并运算产生结果的语法结构。一个表达式会产生一个值,它可以放在任何需要一个值的地方,比如,作为一个函数调用的参数。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!