“使用地图代替类来表示数据” -Rich Hickey


19

在Clojure创作者Rich Hickey的这段视频中,他建议使用map来表示数据,而不是像Java那样使用类来表示数据。我不明白该怎么做,因为如果API用户将其简单地表示为地图,他们怎么会知道输入键是什么。

范例

PersonAPI {
    Person addPerson(Person obj);
    Map<String, Object> addPerson(Map<String, Object> personMap);
}

在第二个函数中,API用户如何知道创建人的输入是什么?



我也想知道这一点,并且我觉得示例问题并不能完全回答这个问题。
sydan 2015年

知道我在SE上的某个地方已经看过这个讨论。我相信这是在JavaScript的上下文中,但是参数是相同的。虽然找不到。
塞巴斯蒂安·雷德尔

2
既然Clojure是Lisp,那么您应该做适合Lisp的事情。当您使用Java时,请使用Java代码。
AK_ 2015年

Answers:


12

摘要摘要(TM)

你得到一些东西。

  • 原型继承和克隆
  • 动态添加新属性
  • 同一类的不同版本(规范级别)的对象共存。
    • 属于较新版本(规范级别)的对象将具有额外的“可选”属性。
  • 新旧属性的内省
  • 验证规则的自省(以下讨论)

有一个致命的缺点。

  • 编译器不会为您检查拼写错误的字符串。
  • 自动重构工具不会为您重命名属性键名称-除非您为这些花哨的名称付费。

事实是,您可以使用um,内省来进行内省。通常会发生以下情况:

  • 启用反射。
  • 将大型自省库添加到您的项目中。
  • 使用属性或注释标记各种对象方法和属性。
  • 让自省库发挥作用。

换句话说,如果您不需要与FP进行交互,则不必听Rich Hickey的建议。

最后但并非最不重要(也是最漂亮的),尽管将其String用作属性键最直接,但您不必使用Strings。许多遗留系统(包括Android™)在整个框架中广泛使用整数ID来引用类,属性,资源等。

Android是Google Inc.的商标。


您也可以使两个世界都开心。

对于Java世界,照常实现getter和setter。

对于FP世界,实施

  • Object getPropertyByName(String name)
  • void setPropertyByName(String name, Object value) throws IllegalPropertyChangeException
  • List<String> getPropertyNames()
  • Class<?> getPropertyValueClass(String name)

在这些函数中,是的,代码很丑陋,但是有一些IDE插件可以使用...呃,一个可以读取代码的智能插件来您解决。

Java方面将像往常一样具有出色的性能。他们将永远不会使用代码的丑陋部分。您甚至可能要对Javadoc隐藏它。

FP方面可以编写所需的任何“ leet”代码,并且通常不会因为代码太慢而对您大喊大叫。


通常,在软件开发中,通常使用地图(属性包)代替对象。它不是函数编程或任何特定类型的语言所独有的。对于任何给定的语言,它可能都不是惯用的方法,但是在某些情况下需要使用它。

特别是,序列化/反序列化通常需要类似的技术。

关于“作为对象的地图”的一些一般想法。

  1. 您仍然必须提供用于验证“作为对象映射”的功能。区别在于“作为对象映射”允许更灵活(限制更少)的验证标准。
  2. 您可以轻松地将其他字段添加到“作为对象映射”。
  3. 为了提供对有效对象的最低要求的规范,您将需要:
    • 列出地图中预期的“最低要求”键组
    • 对于每个需要验证其值的密钥,请提供一个值验证功能
    • 如果存在需要检查多个键值的验证规则,请同样提供。
    • 有什么好处?以这种方式提供规范是自省的:您可以编写程序来查询最少需要的一组键,并获取每个键的验证功能。
    • 在OOP中,所有这些都以“封装”的名义汇总到一个黑匣子中。代替机器可读的验证逻辑,调用者只能阅读人类可读的“ API文档”(幸运的是,它存在)。

commonplace对我来说似乎有点坚强。我的意思是按照您的描述使用它,但是它也是图书馆试图尽其所能掩饰的那些臭名昭著的/脆弱的东西(例如字节数组或裸指针)之一。
Telastyn

@Telastyn这种“千头蛇的丑陋头”通常出现在两个系统之间的通信边界上,由于某种原因,由于通信或进程间通道的原因,该通道不允许完整地传送物体。我猜想像协议缓冲区之类的新技术几乎消除了map作为对象的这种古老的用例。可能还有其他有效的用例,但我对此一无所知。
rwong

2
至于致命的缺点,请同意。但是,如果将“容易拼写错误”和“难以重构”属性键名称尽可能地保留在常量或枚举中,则该问题将消失。当然,它确实限制了一些可扩展性:
。– user949300

如果“一个致命的缺点”确实是致命的,那么为什么有些人能够有效地使用它。而且,类和静态类型是正交的-即使可以动态地键入类,也可以在Clojure中定义类。
内森·戴维斯

@NathanDavis(1)我承认我的答案是从静态输入角度(C#)编写的,而我之所以写此答案,是因为我与问问者的观点相同。我承认我缺乏以FP为中心的观点。(2)欢迎来到SE.SE,并且由于您是Clojure中受人尊敬的人物,因此,如果现有的答案不令人满意,请花点时间写下自己的答案。下注减少声誉,新答案会吸引上投票,从而迅速增加声誉。(3)我可以看到“不完整的对象”如何有用-您可以查询给定对象的2个属性(名称,头像),而忽略其余的属性。
rwong

9

那是一个真正了解他在说什么的人的精彩演讲。我建议读者注意整个过程。只有36分钟长。

他的主要观点之一是,简单性为以后的变革提供了机会。选择一个班级代表一个Person正如您所指出的那样,提供了直接的好处,即创建了一个可静态验证的API,但是随之而来的是限制机会或以后增加更改和重用成本的代价。

他的观点是,使用类可能是一个合理的选择,但应该是有意识地选择,并且要充分了解其成本,而且程序员在注意到这些成本方面传统上做得很差,更不用说将它们考虑在内了。随着需求的增长,应该重新评估该选择。

以下是一些代码更改(在谈话中提到了其中的一两个),与使用Person对象列表相比,使用地图列表可能更简单:

  • 将人员发送到REST服务器。(创建用于将原Map语转换为可传输格式的函数是高度可重用的,甚至可能在库中提供。Person对象可能需要自定义代码才能完成相同的工作)。
  • 通过关系数据库查询自动构建人员列表。(再次,一个通用且高度可重用的功能)。
  • 自动生成表单以显示和编辑人员。
  • 使用通用功能来处理高度不均匀的人员数据,例如学生与员工。
  • 获取居住在特定邮政编码中的所有人员的列表。
  • 重用该代码以特定邮政编码获取所有企业的列表。
  • 在不影响其他客户的情况下,向一个人添加客户特定的字段。

我们一直在解决这类问题,并拥有解决方案和工具,但很少有人会开始考虑是否从一开始就选择更简单,更灵活的数据表示会使我们的工作更轻松。


有这个名字吗?说,对象属性映射还是对象属性映射(与ORM在同一行)?
rwong'2

4
Choosing a class to represent a Person provides the immediate benefit of creating a statically-verifiable API... but that comes with the cost of limiting opportunities or increasing costs for change and reuse later on.错误,而且难以置信。它增加您以后进行更改的机会,因为当您进行重大更改时,编译器会自动为您查找并指出需要更新的每个位置,以使整个代码库达到最高速度。在动态代码中,您无法做到这一点,从而使您真正与以前的选择结合在一起!
梅森惠勒

4
@MasonWheeler:您真正要说的是,您比更动态(且类型更宽松)的数据结构更重视编译时类型的安全性。
罗伯特·哈维

1
多态不是一个限于OOP的概念。对于地图,您可能具有包含性多态性(如果元素是地图可以处理的某种类型的子类型)或临时多态性(如果元素被标记为联合)。这是内部。可以在地图上执行的操作也可以是多态的。当我们在元素上使用高阶函数时或在调度时即席使用时的参数多态性。可以使用名称空间或其他形式的可见性管理来实现封装。从根本上说,对象的隔离并不等于将操作分配给数据类型。
siefca 2015年

1
@GillBates为什么这么说?您只是失去了将这些虚拟方法放在“地图内部”的机会-但这正是Rich Hickey所说的,“ ActiveObjects”实际上是一种反模式。您应该将数据视作数据(数据),而不要将其与行为交织在一起。通过分离关注点可以获得巨大的简单性好处。
维吉尔

4
  • 如果数据几乎没有行为,或者内容可能会发生变化,请使用地图。IMO是一种典型的“ javabean”或“数据对象”,它由带有N个字段,N个设置程序和N个获取程序的Anemic域模型组成,浪费时间。不要通过将其包装在幻想的班级中而用荣耀的结构来打动别人。老实说,明确您的意图,然后使用地图。(或者,如果对您的域有意义,则使用JSON或XML对象)

  • 如果数据具有明显的实际行为,又名方法(“告诉”,“不问”),则使用一个类。并为使用真正的面向对象的编程(-)轻拍自己。

  • 如果数据具有很多基本的验证行为和必填字段,请使用一个类。

  • 如果数据具有中等数量的验证行为,那就是临界点。

  • 如果数据触发了属性更改事件,则使用Map实际上更容易,也不会那么乏味。只需编写一个子类。

  • 使用Map的一个主要缺点是用户必须将值强制转换为Strings,ints,Foos等。如果这很烦人且容易出错,请考虑一个类。或者考虑一个将相关Mapter与相关getter包装在一起的辅助类。


1
实际上,Rich Hickey认为,如果数据具有明显的实际行为,则可能是整个“设计”事情做错了。数据是“信息”。现实世界中的信息不是“存储数据的地方”。信息没有“控制信息变化方式的操作”。我们不会通过告诉人们信息的存储位置来传达信息。有时,面向对象的隐喻是世界的合适模型……但并非总是如此。他就是这么说的-“思考ypur问题”。并非所有事物都是对象-很少有事物。
维吉尔

0

a的API map有两个级别。

  1. 地图的API。
  2. 应用程序的约定。

可以按照约定在地图中描述API。例如,该对:api api-validate可以放置在地图中,:api-foo validate-foo也可以是约定。地图甚至可以存储api api-documentation-link

使用约定,程序员可以创建特定于域的语言,以标准化跨实现为地图的“类型”的访问。使用(keys map)允许在运行时确定属性。

地图没有什么神奇的东西,物体也没有什么神奇的东西。全部派遣。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.