功能编程能替代GoF设计模式吗?
功能编程能替代GoF设计模式吗?
自从去年我开始学习F#和OCaml以来,我已经读了很多文章,坚称设计模式(尤其是在Java中)是面向过程语言中缺失功能的解决方法。我发现的一篇文章提出了一个相当强烈的观点:
大多数我遇到的人都读过Gang of Four(GoF)的《设计模式》。任何自我尊重的程序员都会告诉你,这本书是与语言无关的,模式适用于软件工程,无论你用什么语言。这是一个崇高的说法。不幸的是,它与事实相去甚远。
函数式语言非常表达能力强。在函数式语言中,一个人不需要设计模式,因为语言很可能是如此高级,以至于您最终会编写消除所有设计模式的概念的程序。
函数式编程(FP)的主要特点包括函数作为一等值、柯里化、不可变值等。对我来说,面向对象的设计模式似乎没有逼近这些特点之一。
此外,在支持面向对象编程的函数式语言中(如F#和OCaml),对我来说,使用这些语言的程序员将使用其他任何面向对象语言中找到的相同的设计模式。实际上,现在我每天都在使用F#和OCaml,我在这些语言中使用的模式与我在Java中编写时使用的模式之间没有明显差异。有没有说法称函数式编程消除了面向对象设计模式的需要?如果是,你能否发布或链接到一个典型的面向对象设计模式及其函数式等价物的示例?
函数式编程是否真的能够消除OOP设计模式的需要?
函数式编程和面向对象编程不一样,面向对象的设计模式不适用于函数式编程。相反,你需要使用函数式编程的设计模式。
在函数式编程中,你不会阅读面向对象的设计模式书籍,而是会阅读其他关于函数式编程设计模式的书籍。
对于编程语言无关的吗?
不完全是对于面向对象编程语言来说是编程语言无关的。但是这些设计模式在过程式编程语言中根本不适用,在关系型数据库设计中几乎没有意义,并且在设计电子表格时也不适用。
典型的面向对象设计模式及其函数式等效设计模式是什么?
这个问题本身是有问题的。这就像要求将过程式代码重写为面向对象代码一样。如果我将原始的Fortran(或C)翻译成Java,我没有做任何其他事情,只是进行了翻译。如果我完全将其重写为面向对象编程范式,它将不再像原始的Fortran或C代码,而是完全无法被认出。
从面向对象设计到函数式设计,并没有简单的映射。它们是解决问题的不同方法。
函数式编程(像所有编程风格一样)具有设计模式。关系型数据库有设计模式,面向对象编程有设计模式,过程式编程有设计模式。所有事物都有设计模式,甚至是建筑设计。
设计模式是建立的一种永恒方式,无论技术或问题域如何,都适用。然而,特定的设计模式适用于特定的问题域和技术。
每个认真思考自己所做的事情的人都会发现设计模式。
你引用的博客有点夸大了它的论点。FP并没有消除设计模式的需要。在FP语言中,“设计模式”这个术语并不常用来描述同样的东西。但是它们确实存在。函数式语言有很多最佳实践规则,形式为“当你遇到问题X时,使用类似Y的代码”,这基本上就是设计模式的含义。
但是,大多数面向对象特定的设计模式在函数式语言中基本上是无关紧要的。
我认为,总体而言,设计模式存在的原因只是为了弥补语言上的不足。如果另一种语言可以轻松解决同样的问题,那么该语言就不需要设计模式来解决这个问题。使用该语言的用户甚至可能不知道该问题的存在,因为在该语言中它并不是一个问题。
以下是《设计模式》四人组对这个问题的看法:
编程语言的选择很重要,因为它会影响人的观点。我们的模式假定了Smalltalk / C ++级别的语言特性,这种选择决定了什么可以轻松实现,什么不可以。如果我们假设过程式语言,我们可能会包括名为“继承”、“封装”和“多态”的设计模式。同样,我们的某些模式受到较少见的面向对象语言的直接支持。例如,CLOS具有多方法,这减少了像访问者这样的模式的需求。实际上,Smalltalk和C ++之间有足够的差异,意味着其中一种语言中的一些模式比另一种语言更容易表达。(例如,参见迭代器。)
(上述内容摘自《设计模式》一书引言第4页第3段)
函数式编程的主要特征包括将函数视为一等公民、柯里化、不可变值等。我不认为面向对象的设计模式逼近了这些特征。
若不是函数式编程语言,你可以简单地将一个函数作为另一个函数的参数传递。但在面向对象编程的语言中,你必须将函数封装在一个类中,实例化该对象,然后将其传递给另一个函数。虽然两种方法的效果相同,但在面向对象编程中,它被称为设计模式,需要花费更多代码。
那么对于命令模式,它不就是对一等函数的近似吗? 🙂
在函数式编程语言中,你可以简单地将一个函数作为另一个函数的参数传递。但在面向对象编程的语言中,你必须将函数封装在一个类中,实例化该对象,然后将其传递给另一个函数。虽然两种方法的效果相同,但在面向对象编程中,它被称为设计模式,需要花费更多代码。
抽象工厂模式又是什么呢,不就是柯里化吗?一次只传递一些参数给函数,以配置最终调用它时所输出的值的类型。
因此,多个GoF设计模式在函数式编程语言中已经被淘汰了,因为存在更强大且更易于使用的替代方案。
但是当然,仍有一些设计模式在函数式编程语言中无法解决。孤例(Creational Pattern)的函数式编程等价物是什么呢?(暂时忽略单例模式通常是可怕的设计模式的事实。)
另外,两种编程方式都有自己的设计模式。只是人们通常不将函数式编程中的它们视为这样的设计模式。
但你可能已经遇到过单子(monads)。它们是什么,不就是“处理全局状态”的设计模式吗?这是面向对象编程语言中非常简单的问题,因此没有类似的设计模式存在。我们不需要为“递增静态变量”或“从该套接字读取”设计模式,因为这只是你要做的事情。将monad视为设计模式就像说带有它们通常操作和零元素的整数是一种设计模式一样荒谬。不,monad是一种数学模式,而不是一种设计模式。在(纯)函数语言中,除非您使用monad“设计模式”或任何允许相同事物的其他方法来解决它,否则副作用和可变状态是不可能的。此外,在支持OOP的函数语言(如F#和OCaml)中,我认为使用这些语言的程序员会使用与其他OOP语言中可用的相同的设计模式。事实上,我每天都在使用F#和OCaml,这些语言与Java中使用的模式之间没有显著的区别。也许是因为您仍在以命令的方式思考?很多人在一生中一直在使用命令语言,所以当他们尝试功能语言时,很难放弃那种习惯。 (我见过一些非常有趣的F#尝试,其中每个函数实际上都是一堆“let”语句,基本上就像你拿走了C程序,并将所有分号替换为“let”。:))但另一个可能性是您还没有意识到,您正在解决在OOP语言中需要设计模式的微不足道的问题。当您使用柯里化或将函数作为另一个参数传递时,请停下来思考如何在OOP语言中实现它。
函数式编程是否真的能够消除 OOP 设计模式的需求?
是的。 🙂
当你使用函数式编程语言编程时,你不再需要 OOP 特定的设计模式。但你仍然需要一些通用的设计模式,比如 MVC 或其他非 OOP 特定的东西,并且需要几个新的函数式编程特定的“设计模式”。所有的编程语言都有它们的不足之处,设计模式通常是我们解决这些不足之处的方式。
总之,你可能会发现尝试使用“更干净”的函数式编程语言很有趣,比如我的个人喜好——ML 或者 Haskell,在这些语言中,当你面对一些新的问题时,你没有 OOP 的支撑可以依靠。
正如预期的那样,一些人反对我把设计模式定义为“修补语言中的不足”,所以这是我的理由:
正如之前已经说过的,大多数设计模式是针对一个特定的编程范式,甚至有时只针对一个特定的编程语言。通常,它们解决的是只存在于那种范式中的问题(例如 FP 中的 Monad 或 OOP 中的抽象工厂)。
为什么 FP 中不存在抽象工厂模式?因为它所尝试解决的问题在那里不存在。
因此,如果在 OOP 语言中存在一些在 FP 语言中不存在的问题,那么显然这是 OOP 语言的不足。这个问题可以解决,但你的语言却没有解决它,而需要你编写大量样板代码来解决它。理想情况下,我们希望编程语言可以神奇地消除所有问题。任何仍然存在的问题从本质上讲都是这种语言的不足。 😉