Clojure语言claims的创建者“开放的,大型的一组函数在开放的,小的,可扩展的抽象集合上运行是算法重用和库互操作性的关键”。显然,它与典型的OOP方法相矛盾,在这种方法中,您创建了大量的抽象(类)和一组相对较少的函数。请提供一本书,一本书中的章节,一篇文章或您的个人经历,详细说明主题:
* MFUFA:“很少抽象的功能”
答案 0 :(得分:18)
编程中有两个主要的“抽象”概念:
[编辑:这两个是双重的。第一个是客户端抽象,第二个实现者端抽象(如果你关心这些事情:在形式逻辑或类型理论方面,它们对应于通用和存在量化(分别)。]
在OO中,该类是用于实现两种抽象的厨房接收器功能。
Ad(1),对于几乎每个“模式”,您都需要定义一个自定义类(或几个)。另一方面,在函数式编程中,您通常需要更轻量级和直接的方法来实现相同的目标,特别是函数和元组。经常指出,例如,GoF中的大多数“设计模式”在FP中是多余的。
Ad(2),如果你没有可变状态在你需要检查的所有地方挥之不去,那么封装需要少一点。你仍然在FP中构建ADT,但它们往往更简单,更通用,因此你需要的更少。
答案 1 :(得分:9)
当您以面向对象的方式编写程序时,您会强调在数据类型方面表达域区域。乍一看,这看起来是个好主意 - 如果我们与用户合作,为什么不上课User
?如果用户出售和购买汽车,为什么不上课Car
?通过这种方式,我们可以轻松维护数据和控制流程 - 它只反映现实世界中事件的顺序。虽然这对于域对象非常方便,但对于许多内部对象(即不反映现实世界中的任何内容但仅出现在程序逻辑中的对象)并非如此好。也许最好的例子是Java中的许多集合类型。在Java(以及许多其他OOP语言)中,有两个数组List
。在JDBC中,ResultSet
也是一种集合,但没有实现Collection
接口。对于输入,您通常会使用InputStream
提供用于顺序访问数据的界面 - 就像链表一样!但是它也没有实现任何类型的集合接口。因此,如果您的代码使用数据库并使用ResultSet
,那么为文本文件和InputStream
重构它将更加困难。
MFUFA原则教会我们不要过多关注类型定义以及常见抽象。出于这个原因,Clojure为所有提到的类型引入了单一抽象 - 序列。任何迭代都会自动强制转换为序列,流只是惰性列表,结果集可以很容易地转换为以前的类型之一。
另一个例子是对结构和记录使用PersistentMap
接口。使用这样的通用接口,可以很容易地创建可重用的子例程,并且不需要花费大量时间来进行重构。
总结并回答您的问题:
UserList
- List<User>
在大多数情况下都足够好。 UserList
(例如,当它应具有许多其他功能时),请将List
和Iterable
接口添加到其定义中。 final
或所有字段都是private
,因此派生类无法访问它们(例如,要向类String
添加新函数,应该实现额外的类{ {1}})。尽管如此,我上面描述的规则使得在OOP代码中使用MFUFA变得更加容易。最好的例子是Clojure本身,它以OO风格优雅地实现,但仍然遵循MFUFA原则。 UPD。我还记得面向对象和功能样式之间差异的另一种描述,可能总结得更好我上面所说的:用OO风格设计程序根据数据类型进行思考< / strong>(名词),而功能风格的设计是在操作方面的思考(动词)。你可能会忘记一些名词是相似的(例如忘记继承),但是你应该永远记住在实践中许多动词做同样的事情(例如有相同或类似的接口)。
答案 2 :(得分:6)
报价的早期版本:
“列表的简单结构和自然适用性反映在令人惊讶的非特异性函数中。在Pascal中,过多的可声明数据结构在函数内引入了一种特殊化,抑制和惩罚了偶然的合作。最好有100个函数在一个数据结构上运行,而不是在10个数据结构上运行10个函数。“
...来自着名的SICP书的前言。我相信这本书有很多关于这个主题的适用材料。
答案 3 :(得分:0)
我认为你没有得到图书馆和程序之间的区别。
运行良好的OO库通常会生成少量抽象,程序使用这些抽象来构建其域的抽象。较大的OO库(和程序)使用继承来创建不同版本的方法并引入新方法。
所以,是的,同样的原则适用于OO库。