自从我去年开始学习F#和OCaml以来,我已经阅读了大量的文章,这些文章坚持认为设计模式(特别是Java)是命令式语言中缺少的功能的变通方法.我发现的一篇文章提出了相当强烈的主张:
我遇到过的大多数人都读过Gang of Four的"设计模式"一书.任何自尊的程序员都会告诉你,这本书与语言无关,而且无论你使用哪种语言,这些模式都适用于软件工程.这是一个崇高的主张.不幸的是,它与事实相去甚远.
功能语言极具表现力.在函数式语言中,人们不需要设计模式,因为语言可能是如此高级,您最终会编写概念,一起消除设计模式.
函数式编程的主要特性包括作为一等值,currying,不可变值等的函数.对我来说,OO设计模式近似于任何这些特性似乎并不明显.
另外,在支持OOP的函数式语言(例如F#和OCaml)中,对我来说很明显,使用这些语言的程序员将使用与其他OOP语言相同的设计模式.事实上,现在我每天都使用F#和OCaml,我在这些语言中使用的模式与我在Java中编写时使用的模式之间没有明显的差异.
功能编程是否消除了对OOP设计模式的需求,是否有任何理由?如果是这样,您是否可以发布或链接到典型OOP设计模式及其功能等效的示例?
你引用的博客文章夸大了它的主张.FP不会消除对设计模式的需求.术语"设计模式"并未广泛用于描述FP语言中的相同内容.但他们存在.函数式语言有很多最佳实践规则,"当遇到问题X时,使用看起来像Y的代码",这基本上就是设计模式.
但是,大多数特定于OOP的设计模式在函数式语言中几乎无关紧要是正确的.
我认为一般来说,设计模式只能用于弥补语言中的缺点,我认为应该特别具有争议性.如果另一种语言可以解决同样的问题,那么其他语言也不需要设计模式.该语言的用户可能甚至不知道问题存在,因为,这不是该语言的问题.
以下是四人帮对此问题的看法:
编程语言的选择很重要,因为它会影响一个人的观点.我们的模式假设具有Smalltalk/C++级语言功能,并且该选择决定了哪些可以轻松实现,哪些不能轻松实现.如果我们假设过程语言,我们可能会包含称为"继承","封装"和"多态"的设计模式.同样,我们的一些模式直接由不太常见的面向对象语言支持.例如,CLOS有多种方法,可以减少对访问者等模式的需求.事实上,Smalltalk和C++之间存在足够的差异意味着某些模式可以在一种语言中比另一种语言更容易表达.(例如,参见Iterator.)
(以上是"设计模式导论"一书中的引用,第4页,第3段)
函数式编程的主要特性包括作为一等值,currying,不可变值等的函数.对我来说,OO设计模式近似于任何这些特性似乎并不明显.
什么是命令模式,如果不是近似的一流函数?:)在FP语言中,您只需将函数作为参数传递给另一个函数.在OOP语言中,您必须将该函数包装在一个类中,您可以实例化该函数,然后将该对象传递给另一个函数.效果是一样的,但在OOP中它被称为设计模式,它需要更多的代码.什么是抽象工厂模式,如果不是currying?将参数一次传递给函数,以配置最终调用它时吐出的值.
所以,有些GoF设计模式在FP语言中变得多余,因为存在更强大和更易于使用的替代方案.
但当然,仍然有设计这些图案不是通过FP语言解决.单身的FP等价物是多少?(暂时忽略单身人士通常使用的可怕模式)
它也有两种方式.正如我所说,FP也有其设计模式,人们通常不会这样认为它们.
但是你可能遇到过monad.如果不是"处理全球国家"的设计模式,它们是什么?这是一个在OOP语言中如此简单的问题,那里不存在等效的设计模式.
我们不需要"增加静态变量"或"从该套接字读取"的设计模式,因为它就是你所做的.
在(纯)函数语言中,副作用和可变状态是不可能的,除非你使用monad"设计模式"或任何允许相同事物的任何其他方法来解决它.
另外,在支持OOP的函数式语言(例如F#和OCaml)中,对我来说很明显,使用这些语言的程序员将使用与其他OOP语言相同的设计模式.事实上,现在我每天都使用F#和OCaml,并且我在这些语言中使用的模式与我在Java中编写时使用的模式之间没有明显的差异.
也许是因为你还在考虑当务之急?很多人在一生中处理命令式语言后,在尝试使用函数式语言时很难放弃这种习惯.(我在F#上看到了一些非常有趣的尝试,其中字面上每个函数只是一串'let'语句,基本上就像你采用了C程序一样,并用'let'替换了所有的分号.:))
但另一种可能性可能是你没有意识到你正在解决一些需要用OOP语言设计模式的问题.
当您使用currying,或将函数作为参数传递给另一个时,请停下来思考如何使用OOP语言执行此操作.
功能编程是否消除了对OOP设计模式的需求,是否有任何理由?
是的.:)当您使用FP语言时,您不再需要特定于OOP的设计模式.但是你仍然需要一些通用的设计模式,比如MVC或其他非OOP特定的东西,你需要一些新的FP特定的"设计模式".所有语言都有它们的缺点,设计模式通常是我们解决它们的方式.
无论如何,你可能会觉得尝试使用"更干净"的FP语言很有意思,比如ML(我个人最喜欢的,至少是出于学习目的),或者Haskell,当你没有OOP拐杖时面对新事物.
正如所料,有些人反对我将设计模式定义为"修补某种语言中的缺点",所以这就是我的理由:正如已经说过的,大多数设计模式都是针对一种编程范式,有时甚至是一种特定语言.通常,它们解决仅存在于该范例中的问题(参见FP的monads或OOP的抽象工厂).为什么FP中不存在抽象工厂模式?因为它试图解决的问题不存在.因此,如果在FP语言中不存在OOP语言中存在问题,那么显然这是OOP语言的缺点.问题可以解决,但是你的语言没有这样做,但是需要一堆样板代码来解决它.理想情况下,我们'所有问题都消失了.任何仍然存在的问题原则上都是语言的缺点.;)
功能编程是否消除了对OOP设计模式的需求,是否有任何理由?
函数式编程与面向对象编程不同.面向对象的设计模式不适用于函数式编程.相反,您拥有函数式编程设计模式.
对于函数式编程,您不会阅读OO设计模式书籍,您将阅读有关FP设计模式的其他书籍.
语言不可知论者
不是完全的.只有语言与OO语言无关.设计模式根本不适用于程序语言.它们在关系数据库设计上下文中几乎没有意义.它们在设计电子表格时不适用.
典型的OOP设计模式及其功能等同?
以上不应该存在.这就像要求将一段程序代码重写为OO代码.嗯...如果我将原始的Fortran(或C)翻译成Java,我除了翻译之外没有做任何其他事情.如果我完全将其重写为OO范例,它将不再像原始的Fortran或C那样 - 它将无法识别.
从OO设计到功能设计没有简单的映射.他们看待问题的方式截然不同.
函数式编程(与所有编程风格一样)具有设计模式.关系数据库有设计模式,OO有设计模式,过程编程有设计模式.一切都有设计模式,甚至建筑物的建筑.
设计模式 - 作为一个概念 - 是一种永恒的构建方式,无论技术或问题领域如何.但是,特定的设计模式适用于特定的问题域和技术.
每个想到他们正在做的事情的人都会发现设计模式.
布莱恩关于语言和模式之间紧密联系的评论是关键所在,
这个讨论的缺失部分是习语的概念.Coplien的书"高级C++"在这里产生了巨大的影响.早在他发现克里斯托弗亚历山大和没有名字的专栏之前(你也不能在不读亚历山大的情况下明智地谈论模式),他谈到了掌握成语在真正学习语言中的重要性.他以C中的字符串副本为例,(*from ++ =*to ++); 您可以将其视为缺失语言特征(或库特征)的绑定,但真正重要的是它比任何部分都更重要的思想或表达单位.
这就是模式和语言试图做的事情,让我们更简洁地表达我们的意图.思想单元越丰富,你表达的思想就越复杂.拥有丰富的,共享的词汇表,从一系列的规模 - 从系统架构到钻头 - 让我们能够进行更加智能的对话,以及对我们应该做什么的想法.
作为个人,我们也可以学习.这是练习的全部内容.我们每个人都能理解和使用我们永远无法想到的事情.语言,框架,图书馆,模式,习语等都在分享知识财富方面占有一席之地.
GOF书明确地与OOP联系在一起 - 标题是设计模式 - 可重用面向对象软件的元素(强调我的).
动态编程中的设计模式由Peter Norvig对这个一般主题进行了深思熟虑,虽然关于"动态"语言而不是"功能性"(有重叠).
这是另一个链接,讨论这个主题:http://blog.ezyang.com/2010/05/design-patterns-in-haskel/
在他的博客文章中,爱德华用Haskell描述了所有23种原始GoF模式.
当你试图在"设计模式"(一般)和"FP与OOP"的水平上看这个时,你会发现的答案充其量是模糊的.
但是,在两个轴上更深入,并考虑特定的设计模式和特定的语言功能,事情变得更加清晰.
因此,例如,当使用具有代数数据类型和模式匹配,闭包,第一类函数等的语言时,某些特定模式(如访问者,策略,命令和观察者)肯定会改变或消失.来自GoF书籍的其他一些模式仍然但是,"坚持下去".
总的来说,我会说,随着时间的推移,特定的模式正在被新的(或刚刚升级的)语言特征所消除.这是语言设计的自然过程; 随着语言变得更高级,以前只能在书中使用示例调用的抽象现在成为特定语言特征或库的应用程序.
(旁白:这是我最近写的一篇博客,其中有关于FP和设计模式的更多讨论的其他链接.)
Norvig的演讲暗示了他们对所有GoF模式的分析,并且他们说23种模式中的16种在函数式语言中具有更简单的实现,或者仅仅是语言的一部分.因此,大概至少有七个是a)同样复杂或b)不存在于语言中.不幸的是,我们没有列举它们!
我认为很明显,GoF中的大多数"创造性"或"结构"模式只是让Java或C++中的原始类型系统做你想做的事情的技巧.但无论你编写什么语言,其余的都值得考虑.
一个可能是Prototype; 虽然它是JavaScript的基本概念,但它必须在其他语言中从头开始实现.
我最喜欢的模式之一是Null Object模式:表示没有东西作为一个对象,没有任何东西.这可能更容易用功能语言建模.然而,真正的成就是观点的转变.
我会说当你有一个像Lisp这样支持宏的语言时,你可以构建自己的特定于域的抽象,抽象通常比一般的习语解决方案好得多.
甚至OO设计模式解决方案也是特定于语言的.设计模式是解决您的编程语言无法解决的常见问题的解决方案.在Java中,Singleton模式解决了一个(简化)问题.在Scala中,除了Class之外,还有一个名为Object的顶级构造.这是懒惰的实例化,只有一个.您不必使用Singleton模式来获取Singleton.这是语言的一部分.
正如其他人所说,存在功能编程特有的模式.我认为摆脱设计模式的问题不是转换到功能性问题,而是语言功能问题.
看看Scala如何消除"单例模式":你只需要声明一个对象而不是一个类.另一个功能,模式匹配,有助于避免访客模式的笨拙.请参阅此处的比较:http: //andymaleh.blogspot.com/2008/04/scalas-pattern-matching-visitor-pattern.html
与F#一样,Scala是OO功能的融合.我不知道F#但它可能有这种功能.
闭包以函数语言存在,但不必限于它们.他们帮助委托模式.
再观察一次.这段代码实现了一个模式:它是如此经典,它是如此的元素,我们通常不认为它是一个"模式",但它肯定是:
for(int i = 0; i < myList.size(); i++) { doWhatever(myList.get(i)); }
像Java和C#这样的命令式语言采用了本质上是一个功能构造来处理这个问题:"foreach".
模式是解决一次又一次看到的类似问题的方法,然后进行描述和记录.所以不,FP不会取代模式; 然而,FP可能会创建新的模式,并使一些当前的"最佳实践"模式"过时".
GoF设计模式是针对作为Simula 67后代的OO语言的编码解决方案,如Java和C++.
设计模式处理的大多数"弊病"都是由以下因素引起的:
静态类型的类,它们指定对象但不是它们自己的对象;
对单个调度的限制(只有最左边的参数用于选择一个方法,其余的参数仅被视为静态类型:如果它们具有动态类型,则由特殊方法对其进行排序);
常规函数调用和面向对象函数调用之间的区别,意味着面向对象的函数不能作为期望常规函数的函数参数传递,反之亦然; 和
区分"基本类型"和"类类型".
这些设计模式中没有一个不会在Common Lisp对象系统中消失,即使解决方案的结构与相应的设计模式基本相同.(此外,该对象系统在GoF书籍之前已有十多年了.在该书首次出版的同一年,Common Lisp成为ANSI标准.)
就函数式编程而言,模式是否适用于它取决于给定的函数式编程语言是否具有某种对象系统,以及它是否在受益于模式的对象系统之后建模.这种类型的面向对象与函数式编程不能很好地混合,因为状态的突变位于前端和中心.
构造和非变异访问与函数式编程兼容,因此可以应用与抽象访问或构造有关的模式:工厂,外观,代理,装饰器,访问者等模式.
另一方面,状态和策略等行为模式可能不会直接应用于功能性OOP,因为状态的突变是其核心.这并不意味着他们不适用; 也许他们以某种方式结合任何可用于模拟可变状态的技巧.
我想插一些Jeremy Gibbons的优秀但有些密集的论文:"设计模式作为高阶数据类型 - 通用程序"和"Iterator模式的本质"(两者都可以在这里找到:http:// www. comlab.ox.ac.uk/jeremy.gibbons/publications/).
这两者都描述了惯用功能结构如何覆盖其他(面向对象)设置中特定设计模式所覆盖的地形.
如果不打开类型系统,就无法进行此讨论.
函数式编程的主要特性包括作为一等值,currying,不可变值等的函数.对我来说,OO设计模式近似于任何这些特性似乎并不明显.
那是因为这些功能没有解决OOP所做的相同问题......它们是命令式编程的替代方案.对OOP的FP答案在于ML和Haskell的类型系统......特别是和类型,抽象数据类型,ML模块,Haskell类型类.
但当然仍然存在FP语言无法解决的设计模式.单身的FP等价物是多少?(暂时忽略单身人士通常使用的可怕模式)
类型组首先做的是消除对单身人士的需求.
您可以查看23列表并消除更多,但我现在没有时间.