函数式编程中需要设计模式吗?

2025-06-07

函数式编程中需要设计模式吗?

如果您有兴趣阅读西班牙语版的这篇文章🇪🇸,请查看我的博客:
The Developer's Dungeon

介绍

从去年年底开始,我开始接触函数式编程(FP),但我在这方面做得还不够好。

我发现有些概念非常难以理解,事情仍然感觉非常不自然,但在整个过程中,我开始注意到我们在面向对象编程(OOP)中经常做的一些事情,这些事情在函数式编程语言中更容易实现,它们需要更少的代码,有时实现是由语言本身默认提供的。

在之前的一篇文章中,我讨论了 SOLID 原则如何应用于 FP,你可以在这里阅读,今天我将做一个类似的分析,但涉及设计模式,特别是原始GoF 书中的一些设计模式


策略模式

让我们从原始定义开始:

策略模式是一种允许在运行时选择算法的设计模式。代码不是直接实现单个算法,而是接收运行时指令,决定使用一组算法中的哪一个。
策略模式允许算法根据使用它的客户端而独立地变化。

这看起来像什么?

替代文本

在 OOP 语言中,这通常涉及为每个策略创建单独的类,它们都实现一个公共接口,然后另一个类使用该策略接口,而实际上并不知道在运行时选择了哪个策略实现,这样我们就可以将选择算法的关注点与策略之间实现的差异分开,同时可以在运行时选择其中一个。

在 FP 中,这种模式变得非常容易实现,并且需要的代码更少,我将使用 JavaScript,但在全功能语言上也同样容易实现。



const strategy1 = () => {
  console.log('run strategy 1');
};

const strategy2 = () => {
  console.log('run strategy 2');
};

const consumer = (runStrategy) => {
  /*
  Do other stuff
  */

  runStrategy();
}

const selectedStrategy = condition ? strategy1 : strategy2;

consumer(selectedStrategy);


Enter fullscreen mode Exit fullscreen mode

如你所见,在函数式编程中,策略模式变得像传递函数作为参数一样简单。由于我们不必依赖类或继承来实现相同的功能,因此所需的代码量要少得多。

工厂模式

工厂方法模式是一种使用方法来处理创建对象的问题的模式,它无需指定要创建对象的具体类。创建对象是通过调用工厂方法来实现的——工厂方法要么在接口中指定并由子类实现,要么在基类中实现并由派生类选择性地重写——而不是调用构造函数。

在面向对象编程 (OOP) 语言中,这通常涉及单独的类,这些类封装了创建对象的业务逻辑,并可以决定在运行时创建哪个对象。这通常涉及根据我们想要实例化的对象,在创建的对象上设置特定的参数。

在 FP 中,这种模式变得非常容易实现:



const behavior1 = () => {
  console.log('do behavior 1');
};

const behavior2 = () => {
  console.log('do behavior 2');
};

const factory = (condition) => {
  /*
  Do other stuff
  */

  if (condition) behavior1; 

  return behavior2;
}


Enter fullscreen mode Exit fullscreen mode

如您所见,高阶函数再次为我们提供了一个简单的解决方案,我们不需要通过类继承来定义行为,只需返回一个封装所需行为或对象的函数。

装饰器模式

装饰器模式是一种设计模式,它允许动态地将行为添加到单个对象,而不会影响同一类中其他对象的行为。装饰器模式通常有助于遵循单一职责原则,因为它允许将功能划分到具有特定关注领域的类中。

在 OOP 语言中,您可以将一个对象包装到另一个对象中,并提供原始对象所没有的额外功能,而无需实际修改其实现,此外,您可以在运行时选择需要添加到现有对象的功能。

在函数式编程中,这是通过 来实现的Composition。函数式编程的核心思想之一是将功能分解成可以组合成其他行为的小函数。因此,你不需要使用一个类来包装它,而是使用像LEGO块这样的小函数,并将它们组合在一起来创建新的行为。让我们看一个例子:



const compose = (...fns) => x => fns.reduceRight ((v, f) => f(v), x);

function isBiggerThanThree(value) {
  return value > 3
}

function mapBoolToHumanOutput(value) {
  return value ? "yes": "no"
}

const biggerThanThreeAndMapOutput = compose(
  mapBoolToHumanOutput,
  isBiggerThanThree
)

biggerThanThreeAndMapOutput(3)


Enter fullscreen mode Exit fullscreen mode

在这个例子中,我们定义了两个函数,它们分别执行一件小事,然后将它们组合起来以产生新的行为,只要组合适用,我们就可以继续向组合中添加更多功能😄

观察者模式

观察者模式是一种软件设计模式,其中一个对象(称为主体)维护其依赖项列表(称为观察者),并自动通知它们任何状态变化,通常通过调用它们的某个方法。

此模式基于“推与拉”的理念。与其让一个对象不断检查另一个对象的状态是否发生变化,不如让第二个对象在自身状态发生变化时通知所有其他对象。

在函数式编程中,回调、事件和观察者是极其常用的模式。它们在RX.js等库中也被广泛使用,例如:Reactive Functional Programming

我将参考 Stack Overflow 中“什么是回调?”的答案。

回调

单例模式

单例模式是一种软件设计模式,它将一个类的实例化限制为一个“单一”实例。当只需要一个对象来协调整个系统的操作时,这种方法非常有用。该术语源于数学概念“单例”。

在 OOP 语言中,这涉及创建对象的静态实例,以使其无需实例化即可在任何地方使用,并确保如果有人尝试再次实例化它,则返回前一个实例。

在函数式编程中,这种模式变得完全没有必要,因为数据与函数完全分离,不需要向整个应用程序暴露全局状态。此外,所有函数都存在于全局命名空间中,始终可访问,它们接收输入并产生结果,而不会影响外部世界。

结论

正如您所见,OOP 世界中发展起来的一些模式似乎为这些语言带来了 FP 免费获得的好处,同时又不失去 OOP 的特征,例如对状态和数据的控制。

还有一些其他模式我没有提到,因为它们适合前面提到的示例所提出的解决方案。


随着我继续探索函数式编程,我可能会找到更多符合我这个假设的例子和案例。如果你喜欢这篇文章,请在评论区告诉我,我会继续分享。

别忘了分享哦😄

文章来源:https://dev.to/patferraggi/do-you-need-design-patterns-in-functions-programming-370c
PREV
面向对象开发人员的函数式编程 - 第 0 部分
NEXT
SOLID 原则适用于函数式编程吗?