由于在 OOP 中您可以将很多细节封装(隐藏)为类中的私有(private)字段,因此您可以隐藏大部分细节。因此,当您想要更改某些内容(重构)时,“通常”会更容易,因为大多数时候更改的范围是有限的。
另一方面,在函数式编程中,如果要更改某些内容(添加字段或更改函数输入/输出),则必须在整个软件中查找该元素的每次出现并更新它们,有时(在软件的情况下)框架,用户在当前代码库之外),这可能是不可能的,并导致向后不兼容的更改。
最佳答案
起点
我们将从一个依赖于两个独立合约——Pair 和 List 的小程序开始。只要契约(Contract)得到履行,这些契约(Contract)的实现几乎可以是任何事情
例如,配对合约给出 cons
, head
, 和 tail
– head(cons(a,b))
必须返回 a
– 同样,tail(cons(a,b))
必须返回 b
.
这种创建一组函数来与数据交互的技术称为数据抽象——如果你对该主题感兴趣,我在网站上还有其他几个答案可以讨论它——本文底部的链接
// -------------------------------------------------
// pair contract
// head(cons(a,b)) == a
// tail(cons(a,b)) == b
const cons = (x,y) =>
[x,y]
const head = pair =>
pair[0]
const tail = pair =>
pair[1]
// -------------------------------------------------
// list contract
// list() == empty()
// list(a,b,c) == cons(a, cons(b, cons(c, empty())))
const empty = () =>
null
const list = (x,...xs) =>
x === undefined ? empty() : cons(x, list(...xs))
// -------------------------------------------------
// demo
const sum = xs =>
xs === empty()
? 0
: head(xs) + sum(tail(xs))
console.log(sum(list(1,2,3))) // 6
第一次重构:对
现在我将通过重新实现
cons
来重构 Pair 代码。 , head
和 tail
– 注意我们没有触及列表代码 empty
或 list
,以及演示代码 sum
不需要改变// -------------------------------------------------
// pair contract
// head(cons(a,b)) == a
// tail(cons(a,b)) == b
const cons = (x,y) =>
f => f(x,y)
const head = pair =>
pair((x,y) => x)
const tail = pair =>
pair((x,y) => y)
// -------------------------------------------------
// list contract
// list() == empty()
// list(a,b,c) == cons(a, cons(b, cons(c, empty())))
const empty = () =>
null
const list = (x,...xs) =>
x === undefined ? empty() : cons(x, list(...xs))
// -------------------------------------------------
// demo
const sum = xs =>
xs === empty()
? 0
: head(xs) + sum(tail(xs))
console.log(sum(list(1,2,3))) // 6
第二次重构:列表
现在我要更改 List 实现,但仍要确保契约(Contract)已履行 - 请注意,我不必更改 Pair 实现,并且演示代码保持不变
// -------------------------------------------------
// pair contract
// head(cons(a,b)) == a
// tail(cons(a,b)) == b
const cons = (x,y) =>
f => f(x,y)
const head = pair =>
pair((x,y) => x)
const tail = pair =>
pair((x,y) => y)
// -------------------------------------------------
// list contract
// list() == empty()
// list(a,b,c) == cons(a, cons(b, cons(c, empty())))
const __EMPTY__ = Symbol()
const empty = () =>
__EMPTY__
const list = (...xs) =>
xs.reduceRight((acc,x) => cons(x,acc), empty())
// -------------------------------------------------
// demo
const sum = xs =>
xs === empty()
? 0
: head(xs) + sum(tail(xs))
console.log(sum(list(1,2,3))) // 6
一个又一个...
我们实现的合约有效地封装了实现细节,就像 OO 程序中的私有(private)数据/方法一样。注意
cons
在第一个示例中返回一个数组,但在第二个示例中返回一个 lambda(函数)——这个细节无关紧要,因为 cons
的用户如果对应的 head
仍然保证正确的数据和 tail
使用访问器。只要契约(Contract)仍然履行,我们可以根据需要多次更改实现细节。我们甚至可以像
__EMPTY__
那样引入新的数据/代码。在最后一个例子中。用户仍然只能使用 list
和 empty
以保证正确的行为。更多关于数据抽象的答案
关于oop - 函数式编程中的重构是否比 OOP 中的重构更困难?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44602945/