"许多功能在很少抽象的基础上运行"原则与OOP相比

Ale*_*xey 15 oop functional-programming clojure reusability

Clojure语言的创建者声称 "开放的,大型的函数集在一个开放的,小的,可扩展的抽象集合上运行是算法重用和库互操作性的关键".显然,它与典型的OOP方法相矛盾,在这种方法中,您创建了大量的抽象(类)和一组相对较少的函数.请建议一本书,一本书中的章节,一篇文章或您的个人经历,详细说明这些主题:

  1. 激发OOP中出现的问题的例子以及如何使用"很少抽象的许多函数"来解决这些问题
  2. 如何有效地做MFUFA*设计
  3. 如何重构针对MFUFA的OOP代码
  4. OOP语言的语法如何影响MFUFA

*MFUFA:"很少抽象的功能"

And*_*erg 18

编程中有两个主要的"抽象"概念:

  1. 参数化("多态",通用性).
  2. 封装(数据隐藏),

[编辑:这两个是双重的.第一个是客户端抽象,第二个实现者抽象(如果你关心这些事情:在形式逻辑或类型理论方面,它们分别对应于普遍存在量化).

在OO中,该类是用于实现两种抽象的厨房接收器功能.

Ad(1),几乎每个"模式"都需要定义一个自定义类(或几个).另一方面,在函数式编程中,您通常需要更轻量级和直接的方法来实现相同的目标,特别是函数和元组.经常指出,例如,来自GoF的大多数"设计模式"在FP中是多余的.

Ad(2),如果您没有可变状态在您需要检查的所有地方挥之不去,则需要稍微减少封装.您仍然在FP中构建ADT,但它们往往更简单,更通用,因此您需要更少的ADT.

  • 这种区别是至关重要的,每个人都应该阅读Scott Meyers关于封装的[this](http://www.drdobbs.com/cpp/184401197)文章. (2认同)

ffr*_*end 9

当您以面向对象的方式编写程序时,您将重点放在根据数据类型表达域区域.乍一看,这看起来是个好主意 - 如果我们与用户合作,为什么不上课User呢?如果用户出售和购买汽车,为什么不上课Car呢?通过这种方式,我们可以轻松维护数据和控制流程 - 它只反映现实世界中事件的顺序.虽然这对于域对象来说非常方便,但对于许多内部对象(即不反映现实世界中的任何内容但仅出现在程序逻辑中的对象),它并不是那么好.也许最好的例子是Java中的许多集合类型.在Java(以及许多其他OOP语言)中,有两个数组List.在JDBC中,ResultSet它也是一种集合,但不实现Collection接口.对于输入,您经常会使用InputStream它提供接口来顺序访问数据 - 就像链表一样!但是它也没有实现任何类型的集合接口.因此,如果您的代码使用数据库并使用ResultSet它将更难为文本文件重构它InputStream.

MFUFA原则教会我们不要太注意类型定义,更多注意常见的抽象.出于这个原因,Clojure为所有提到的类型引入了单一抽象 - 序列.任何迭代都会自动强制转换为序列,流只是惰性列表,结果集可以很容易地转换为以前的类型之一.

另一个例子是使用PersistentMap结构和记录的接口.使用这样的通用接口,可以很容易地创建可重用的子例程,并且不需要花费大量时间来进行重构.

总结并回答您的问题:

  1. OOP中出现的问题的一个简单示例:从许多不同来源(例如DB,文件,网络等)读取数据并以相同方式处理它.
  2. 为了实现良好的MFUFA设计,尽量使抽象尽可能通用,并避免临时实现.例如,避免类型a-la UserList- List<User>在大多数情况下足够好.
  3. 遵循第2点的建议.此外,尝试尽可能多地为您的数据类型(类)添加接口.例如,如果您确实需要UserList(例如,当它应该具有许多附加功能时),请将两者ListIterable接口添加到其定义中.
  4. OOP(至少在Java和C#中)不太适合这个原则,因为它们试图在初始设计期间封装整个对象的行为,因此很难为它们添加更多的功能.在大多数情况下,您可以扩展有问题的类并将您需要的方法放入新对象中,但是1)如果其他人实现了自己的派生类,它将与您的不兼容; 2)有时候类是final或者是所有字段private,所以派生类不能访问它们(例如,向第一类添加新函数String应该实现额外的类StringUtils).尽管如此,我上面描述的规则使得在OOP代码中使用MFUFA变得更加容易.最好的例子是Clojure本身,它以OO风格优雅地实现,但仍然遵循MFUFA原则.

UPD.我记得面向对象和功能样式之间差异的另一种描述,可能总结得更好我上面所说的:OO风格的设计程序是根据数据类型(名词)来思考,而功能风格的设计则是根据操作思考(动词).你可能会忘记一些名词是相似的(例如忘记继承),但是你应该永远记住在实践中许多动词做同样的事情(例如有相同或类似的接口).


Raf*_*ird 6

更早版本的报价:

"列表的简单结构和自然适用性反映在惊人的非特异性函数中.在Pascal中,过多的可声明数据结构在函数内引入了一种特殊化,抑制和惩罚偶然的合作.最好有100个函数在一个数据结构上运行而不是让10个功能在10个数据结构上运行."

......来自着名的SICP书的前言.我相信这本书有很多关于这个主题的适用材料.


Mar*_*cin 0

我认为您没有意识到图书馆和程序之间存在差异。

运行良好的 OO 库通常会生成少量抽象,程序使用这些抽象为其领域构建抽象。较大的面向对象库(和程序)使用继承来创建不同版本的方法并引入新方法。

所以,是的,同样的原则也适用于 OO 库。

  • 不同意,客户端代码中也存在差异。我在 Clojure 中仅使用映射和序列来对所有数据进行建模,而在 Java 中,我被迫不断为每个用例发明新的类型安全容器。应该注意的是,主要区别不在于 OOP 和 FP 之间,而在于静态类型和动态类型之间。 (2认同)
  • Marko,1。我同意你的观点,差异也在于客户端代码。2. 我不同意这是关于静态类型与动态类型的问题。目前我正在使用 Ruby 进行编程。与 Java 相比,代码小了 3 倍,但大规模结构是相同的:我们有很多类,并且一些方法耦合到这些类。很少重复使用。我不喜欢那样 (2认同)
  • 不,我特别**不**想要静态安全的容器,但如果我没有它们,那么由于所有的向下转换,我的代码会变得更加混乱。在 Java 中,您需要类型才能调用函数。您是否尝试过使用“Map&lt;Object,Object&gt;”编写 Java 代码?关于抽象数量的关键实际上在于您编写该抽象的次数,而不是某个描述级别的不同抽象的数量。 (2认同)