当您以面向对象的风格编写程序时,您会强调用数据类型来表达领域。乍一看,这似乎是个好主意——如果我们与用户一起工作,为什么不开设课程User
呢?如果用户买卖汽车,为什么不上课Car
呢?这样我们就可以轻松地维护数据和控制流——它只是反映了现实世界中事件的顺序。虽然这对于领域对象来说非常方便,但对于许多内部对象(即不反映来自现实世界的任何东西,但只出现在程序逻辑中的对象)来说,它并不是那么好。也许最好的例子是 Java 中的一些集合类型。在 Java(和许多其他 OOP 语言)中,有两个数组,List
s。在 JDBC 中有ResultSet
这也是一种集合,但不实现Collection
接口。对于输入,您将经常使用InputStream
它为顺序访问数据提供接口 - 就像链表一样!但是它也没有实现任何类型的集合接口。因此,如果您的代码与数据库一起使用并使用ResultSet
它,那么将其重构为文本文件和InputStream
.
MFUFA 原则告诉我们少关注类型定义,多关注常见抽象。出于这个原因,Clojure 为所有提到的类型引入了单一抽象 - 序列。任何可迭代对象都会自动强制进行排序,流只是惰性列表,结果集可以轻松转换为以前的类型之一。
另一个例子是使用PersistentMap
结构和记录的接口。有了这样的通用接口,创建可重用的子例程就变得非常容易,并且不需要花费大量时间进行重构。
总结并回答您的问题:
- OOP 中经常出现的问题的一个简单示例:从许多不同的来源(例如数据库、文件、网络等)读取数据并以相同的方式处理它。
- 为了做出好的 MFUFA 设计,尽量使抽象尽可能通用并避免临时实现。例如,避免类型 a-la
UserList
-List<User>
在大多数情况下就足够了。
- 遵循第 2 点的建议。此外,尝试向您的数据类型(类)添加尽可能多的接口。例如,如果你真的需要
UserList
(例如当它应该有很多额外的功能时),在它的定义中添加List
和Iterable
接口。
- OOP(至少在 Java 和 C# 中)不太适合这一原则,因为它们在初始设计时试图封装整个对象的行为,因此很难向它们添加更多功能。在大多数情况下,您可以扩展有问题的类并将所需的方法放入新对象中,但是 1)如果其他人实现了他们自己的派生类,它将与您的不兼容;2) 有时类
final
或所有字段都已创建private
,因此派生类无法访问它们(例如,向类添加新功能String
应实现附加类StringUtils
)。尽管如此,我上面描述的规则使得在 OOP 代码中使用 MFUFA 变得更加容易。最好的例子是 Clojure 本身,它以 OO 风格优雅地实现,但仍然遵循 MFUFA 原则。
UPD。我记得对面向对象和函数式风格之间差异的另一种描述,这可能更好地总结了我上面所说的一切:以OO风格设计程序是根据数据类型(名词)来思考,而以函数式设计是根据操作来思考(动词)。您可能会忘记一些名词是相似的(例如忘记继承),但您应该始终记住许多动词在实践中做同样的事情(例如具有相同或相似的接口)。