面向数据编程 Data-Oriented Programming [4]
作者:互联网
0.3 DO原则#2:用通用数据结构表示数据实体
0.3.1原理简述
当我们坚持原则#1的时候,代码与数据是分开的。DO对组织代码所使用的编程结构没有意见,但它对数据应该如何表示有很多意见。这就是原则#2的主题。
NOTE 原则2:用通用数据结构表示你的应用程序的数据结构。
最常见的通用数据结构是Map(字典)和数组。但是,使用其他的通用数据结构也是完全可以的(例如,集合、列表、队列…)
原则#2不涉及数据的可变性或不可变性。这就是原则3的主题:数据是不可改变的。
0.3.2 原则#2的说明
在DO中,我们用通用数据结构(如地图和数组)来表示我们的数据,而不是通过特定的类来实例化数据。
事实上,大多数出现在典型应用中的数据实体都可以用Map(字典)和数组表示。但也存在其他的通用数据结构(如集合、列表、队列…),在某些用例中可能需要。
让我们看一下与用来说明原则#1的同样简单的例子:代表作者的数据。
一个作者是一个数据实体,有一个名字,一个姓氏和他/她写的书的数量。
当我们使用一个特定的类来代表作者时,我们就违反了原则#2,如清单0.11中的内容
清单0.11 破坏OOP中的第2条原则
class AuthorData {
constructor(firstName, lastName, books) {
this.firstName = firstName;
this.lastName = lastName;
this.books = books;
}
}
当我们使用Map(字典)–这是一个通用的数据结构–来表示作者时,我们是符合原则#2的,比如在清单0.12中。
清单0.12 遵循OOP中的第2条原则
function createAuthorData(firstName, lastName, books) {
var data = new Map;
data.firstName = firstName;
data.lastName = lastName;
data.books = books;
return data;
}
在像JavaScript这样的语言中,Map(字典)也可以通过一个data literal>>来实例化,这就更方便了。清单0.13中显示了一个例子。
清单0.13 遵循原则#2,使用Map(字典)literals
function createAuthorData(firstName, lastName, books) {
return {
firstName: firstName,
lastName: lastName,
books: books
};
}
0.3.3 原则#2的好处
当我们使用通用数据结构来表示我们的数据时,我们的程序会受益于:
- 充分利用不限于我们特定用例的通用功能
- 灵活的数据模型
利用不限于我们特定用例的功能
Alan Perlis有一句名言,很好地概括了这一好处:
让100个函数在一个数据结构上操作,比10个函数在10个数据结构上操作要好。
—— Alan Perlis
当我们使用通用数据结构来表示实体时,除了由第三方库提供的功能之外,我们还有权使用编程语言本身在这些数据结构上提供的丰富函数集来操作实体。
例如,JavaScript本身就提供了一些关于Map(字典)和数组的基本函数,而第三方库(如lodash)则使用更多函数扩展了该功能。
例如,当作者被表示为Map(字典)时,我们可以使用JSON.stringify()将其免费序列化为JSON,JSON.stringify()是JavaScript的一部分,如清单0.14所示。
清单0.14当我们遵循原则#2时,数据序列化是容易的
var data = createAuthorData("Isaac", "Asimov", 500);
JSON.stringify(data);
如果我们想序列化作者数据,而不想序列化图书数量,我们可以使用lowash的_.ick()函数来创建一个带有键的子集的对象。清单0.15显示了一个示例。
清单0.15使用泛型函数操作数据
var data = createAuthorData("Isaac", "Asimov", 500);
var dataWithoutBooks = _.pick(data, ["firstName", "lastName"]);
JSON.stringify(dataWithoutBooks);
TIP 当您遵循原则#2时,可以使用一组丰富的功能来操作您的数据实体。
灵活的数据模型
当我们使用通用数据结构时,我们的数据模型是灵活的,因为我们的数据不会被强制遵循特定的形状。因此,我们可以自由创建没有预定义形状的数据。我们还可以自由修改数据的形状。
在经典的OO中-当我们不遵守原则#2时-每段数据都是通过一个类实例化的,并且必须遵循严格的数据形状。因此,即使需要稍微不同的数据形状,我们也必须定义一个新类。
以AuthorData类为例,它表示由3个字段组成的AuthorData实体:FirstName、LastName和Books。假设您想添加一个字段fullName,其中包含作者的全名。
如果不遵循原则2,则必须定义一个新的AuthorDataWithFullName类。
但是,当您使用通用数据结构时,您可以随时向Map(字典)添加(或删除)字段,如清单0.16所示。
清单0.16动态添加字段
var data = createAuthorData("Isaac", "Asimov", 500);
data.fullName = "Isaac Asimov";
TIP 在数据形状往往非常动态的应用程序(例如Web应用程序和Web服务)中,使用灵活的数据模型尤其有用。
在第3章中,我们将详细探讨灵活数据模型在实际应用程序环境中的好处。
0.3.4原则#2的代价
天下没有免费午餐这回事。应用原则#2是要付出代价的。
当我们用通用数据结构表示数据实体时,我们必须付出的代价是:
- 性能受到轻微影响
- 需要手动记录数据形状
- 没有编译时检查数据是否有效
代价 #1:性能受到影响
当我们使用特定的类来实例化数据时,检索类成员的值非常快。原因是编译器预先知道数据将是什么样子,它可以进行各种优化。
对于泛型数据结构,更难进行优化。因此,检索Map(字典)中与键相关联的值比检索类成员的值要慢一些。同样,在Map(字典)中设置任意键的值比设置类成员的值要慢一些。
TIP 从Map(字典)中检索和存储与任意键相关联的值比使用类成员要慢一些。
在大多数编程语言中,这种性能影响并不显著,但要记住这一点。
代价#2:需要手动记录数据形状
从类实例化对象时,数据形状的信息在类定义中。它对开发人员和IDE(想想自动完成功能)都很有帮助。
TIP 当我们使用通用数据结构存储数据时,需要手动记录数据的形状。
即使当我们足够严格地记录代码时,也可能会稍微修改实体的形状,而忘记更新文档。
在这种情况下,我们必须研究代码,以便找出数据的真实形状。
在本书的第三部分中,我们将探讨如何解决这个问题。
代价#3:没有编译时检查数据是否有效
再次查看我们在探索原则#1期间创建的fullName函数:
清单0.17 将其操作的数据作为参数接收的函数
function fullName(data) {
return data.firstName + " " + data.lastName;
}
当我们将一段不符合fullName预期形状的数据传递给fullName时,在运行时会发生错误。
例如,我们可能错误地键入了存储名字的字段(fistName而不是firstName),并且我们得到了一个奇怪的结果,其中从结果中省略了firstName,而不是编译时错误或异常:
清单0.18 数据与预期形状不一致时的奇怪行为
fullName({fistName: "Issac", lastName: "Asimov"}); // it returns "undefined Asimov"
当仅通过具有刚性数据形状的类实例化数据时,将在编译时捕获此类错误。
TIP 当使用通用数据结构表示数据时,仅在运行时捕获数据形状错误。
0.3.5 总结
DO指导我们使用通用数据结构来表示我们的数据。
这可能会对性能造成(很小的)影响,并迫使我们手动记录数据的形状,因为我们不能依赖编译器来静态验证它。
但这是值得的,因为当我们坚持这一原则时,我们可以使用丰富的通用函数集(由语言和第三方库提供)来操作数据实体,并且我们的数据模型是灵活的。
此时,数据可以是可变的,也可以是不可变的。下一个原则将引导我们走向不变性。
旁白
DO原则#2:用通用数据结构表示数据实体
原则
使用通用数据结构表示你的应用程序的数据 (主要是Map和数组)。
好处:
- 充分利用不限于我们特定用例的通用功能
- 灵活的数据模型
代价:
- 性能打击
- 数据形状需要手动记录
- 没有在编译时检查数据是否有效
标签:Map,firstName,数据,data,Programming,Oriented,数据结构,Data,我们 来源: https://blog.csdn.net/u013716859/article/details/118020932