在函数式编程中重构比在 OOP 中更难吗?

mah*_*dix 5 oop refactoring functional-programming

由于在 OOP 中您可以将很多细节封装(隐藏)为类中的私有字段,因此您可以隐藏大部分细节。因此,当您想要更改某些内容(重构)时,“通常”会更容易,因为在大多数情况下,更改的范围是有限的。

另一方面,在函数式编程中,如果您想更改某些内容(添加字段或更改函数输入/输出),您必须在整个软件中查找该元素的每次出现并更新它们,有时(如果是软件框架,其中用户在当前代码库之外),这可能是不可能的,并且会导致向后不兼容的更改。

Tha*_*you 4

初始点

\n\n

我们将从一个依赖于两个独立合约 \xe2\x80\x93 Pair 和 List 的小程序开始。只要履行合同,这些合同的实施实际上可以是任何事情

\n\n

例如,配对合约给出consheadtail\xe2\x80\x93head(cons(a,b)) 必须返回a\xe2\x80\x93 同样,tail(cons(a,b)) 必须返回b

\n\n

这种创建一组函数来与数据交互的技术称为数据抽象\xe2\x80\x93 如果您对该主题感兴趣,我在网站上还有其他几个讨论它的答案 \xe2 \x80\x93 链接位于本文底部

\n\n

\r\n
\r\n
// -------------------------------------------------\r\n// pair contract\r\n// head(cons(a,b)) == a\r\n// tail(cons(a,b)) == b\r\n\r\nconst cons = (x,y) => \r\n  [x,y]\r\n\r\nconst head = pair =>\r\n  pair[0]\r\n  \r\nconst tail = pair =>\r\n  pair[1]\r\n\r\n// -------------------------------------------------\r\n// list contract\r\n// list()      == empty()\r\n// list(a,b,c) == cons(a, cons(b, cons(c, empty())))\r\n\r\nconst empty = () =>\r\n  null\r\n  \r\nconst list = (x,...xs) =>\r\n  x === undefined ? empty() : cons(x, list(...xs))\r\n\r\n// -------------------------------------------------\r\n// demo\r\nconst sum = xs =>\r\n  xs === empty()\r\n    ? 0\r\n    : head(xs) + sum(tail(xs))\r\n    \r\nconsole.log(sum(list(1,2,3))) // 6
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n\n

第一次重构:配对

\n\n

现在我将通过重新实现 来重构 Pair 代码conshead并且tail\xe2\x80\x93 注意到我们没有触及 List 代码emptylist,并且演示代码sum不需要更改

\n\n

\r\n
\r\n
// -------------------------------------------------\r\n// pair contract\r\n// head(cons(a,b)) == a\r\n// tail(cons(a,b)) == b\r\n\r\nconst cons = (x,y) => \r\n  f => f(x,y)\r\n\r\nconst head = pair =>\r\n  pair((x,y) => x)\r\n  \r\nconst tail = pair =>\r\n  pair((x,y) => y)\r\n\r\n// -------------------------------------------------\r\n// list contract\r\n// list()      == empty()\r\n// list(a,b,c) == cons(a, cons(b, cons(c, empty())))\r\n\r\nconst empty = () =>\r\n  null\r\n  \r\nconst list = (x,...xs) =>\r\n  x === undefined ? empty() : cons(x, list(...xs))\r\n\r\n// -------------------------------------------------\r\n// demo        \r\nconst sum = xs =>\r\n  xs === empty()\r\n    ? 0\r\n    : head(xs) + sum(tail(xs))\r\n    \r\nconsole.log(sum(list(1,2,3))) // 6
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n\n

第二次重构:列表

\n\n

现在我要更改 List 实现,但仍然确保履行合同 \xe2\x80\x93 请注意,我不必更改 Pair 实现,并且演示代码保持不变

\n\n

\r\n
\r\n
// -------------------------------------------------\r\n// pair contract\r\n// head(cons(a,b)) == a\r\n// tail(cons(a,b)) == b\r\n\r\nconst cons = (x,y) => \r\n  f => f(x,y)\r\n\r\nconst head = pair =>\r\n  pair((x,y) => x)\r\n  \r\nconst tail = pair =>\r\n  pair((x,y) => y)\r\n\r\n// -------------------------------------------------\r\n// list contract\r\n// list()      == empty()\r\n// list(a,b,c) == cons(a, cons(b, cons(c, empty())))\r\n\r\nconst __EMPTY__ = Symbol()\r\n\r\nconst empty = () =>\r\n  __EMPTY__\r\n  \r\nconst list = (...xs) =>\r\n  xs.reduceRight((acc,x) => cons(x,acc), empty())\r\n\r\n// -------------------------------------------------\r\n// demo\r\nconst sum = xs =>\r\n  xs === empty()\r\n    ? 0\r\n    : head(xs) + sum(tail(xs))\r\n    \r\nconsole.log(sum(list(1,2,3))) // 6
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n\n


\n\n

一个又一个...

\n\n

我们实现的契约有效地封装了实现细节,就像面向对象程序中的私有数据/方法一样。注意如何在第一个示例中返回数组,但在第二个 \xe2\x80\x93 中返回 lambda(函数),这个cons细节并不重要,因为如果使用相应的和访问器,仍然可以保证用户获得正确的数据。consheadtail

\n\n

只要合同仍然履行,我们可以根据需要继续多次更改实施细节。我们甚至可以像上__EMPTY__一个示例中那样引入新的数据/代码。用户仍然只是为了使用listempty保证正确的行为。

\n\n
\n\n

有关数据抽象的更多答案

\n\n\n