面向数据编程 Data-Oriented Programming [16]

3.4 使用泛型函数操作数据

乔:现在,让我向你展示我们是如何利用泛型函数来操作数据的。

你:好,我很好奇你将如何实现图书馆管理系统的搜索功能。

乔:好的。首先,根据图3.3中的数据模型,让我们实例化一个图书馆目录数据的Catalog record,我们只有一本书“守望者”:

清单 3.5 Catalog record

var catalogData = 
	{ "booksByIsbn": {
		"978-1779501127": {
			"isbn": "978-1779501127",
			"title": "Watchmen", 
			"publicationYear": 1987,
			"authorIds": ["alan-moore", "dave-gibbons"], 
			"bookItems": [
				{
					"id": "book-item-1",
					"rackId": "rack-17", 
					"isLent": true
				},
				{
					"id": "book-item-2",
					"rackId": "rack-17", 
					"isLent": false
				}
			]
		}
	},
	"authorsById": 
		{ "alan-moore": {
			"name": "Alan Moore", 
			"bookIsbns": ["978-1779501127"]
		},
		"dave-gibbons": {
			"name": "Dave Gibbons", 
			"bookIsbns": ["978-1779501127"]
		}
	}
}

你:我看到了我们谈到的两个索引,booksByIsbn和AuthsById。如何在DO中区分记录和索引?

乔:在实体图中,记录和索引之间有明显的区别。但在我们的代码中,两者都是纯数据。

你:我想这就是为什么这种方法被称为面向数据编程。

乔:注意,在程序中可视化系统数据的任何部分是多么简单。原因是数据被表示为数据!

你:听起来像个lapalissade

TIP 在DO中,数据表示为数据。

乔:的确,这是显而易见的,但通常在面向对象中,数据是由对象表示的,这使得在程序中可视化数据更具挑战性。

TIP 在DO中,我们可以可视化系统数据的任何部分。

你:你将如何从目录数据中检索特定图书的标题?

乔:这是个很好的问题。事实上,在DO系统中,每条信息都有一条路径,我们可以从中检索信息。

你:我不明白。

乔:例如,目录中“守望者”书名的路径是:[“booksByIsbn”,“978-1779501127”,“Title”]。

你:所以呢?

乔:一旦我们有了一条信息的路径,我们就可以用Lodash的_.get()函数检索信息:

清单3.6 从路径检索书名

_.get(catalogData, ["booksByIsbn", "978-1779501127", "title"])

你:它在Java这样的静态类型语言中能顺利工作吗?

乔:这取决于您是只需要传递该值,还是具体访问该值。

你:我不明白。

乔:想象一下,一旦你得到一本书的书名,你就想把这个字符串转换成一个大写的字符串。然后需要对字符串进行静态强制转换。

清单3.7 将字段值转换为字符串,以便将其作为字符串进行操作

((String)watchmen.get("title")).toUpperCase()

你:这很有道理。Map的值具有不同的类型。因此,编译器将其声明为Map<String,Object>。该字段的类型信息将丢失。

乔:这有点烦人,但通常情况下,代码只是传递数据。因此,我们不必过多地处理静态强制转换。

TIP 在静态类型语言中,我们有时需要静态转换字段值。

你:性能怎么样?

乔:在大多数编程语言中,Map是非常有效的。访问Map中的字段比访问类成员稍微慢一些。通常,这并不重要。

TIP 通过访问Map中的字段而不是类成员,不会对性能造成重大影响。

你:让我们回到信息路径这个概念上来。在OO中,我还可以用catalogData.booksByIsbn[“978-1779501127”].title.访问“守望者”这本书的标题。记录字段的类成员和索引键的字符串。

乔:这是有根本区别的。当记录表示为Map时,可以使用泛型函数(如_.get())通过其路径检索信息。但是当记录被表示为对象时,您需要为每种类型的信息路径编写特定的代码。

你:你说的具体代码是什么意思?CatalogData.booksByIsbn[“978-1779501127”].title?中的具体内容是什么?

乔:在Java这样的静态类型语言中,要编写这段代码,您需要导入Catalog和Book的类定义。

你:是用JavaScript这样的动态类型语言吗?

乔:即使在JavaScript中,当您使用从类实例化的对象表示记录时,也不容易编写一个将路径作为参数接收并显示与此路径对应的信息的函数。您必须为每种路径编写特定的代码。您将使用点符号访问类成员,并使用方括号符号访问映射字段。

你:你是不是说在DO中,信息路径是一等公民?

乔:当然可以!信息路径是一等公民。它可以存储在变量中,并作为参数传递给函数。

TIP 在DO中,您可以通过路径和泛型函数检索每条信息。

图3.5 以树的形式显示目录数据。每条信息都可以通过由字符串和整数组成的路径访问。例如,Alan Moore的第一本书的路径是["catalog", "authorsById", "alan-moore", "bookIsbns", 0]

 

3.5计算搜索结果

你:我开始感受到DO的力量了。

乔:等等。这只是个开始。让我向您展示编写代码来检索图书信息并将其显示在搜索结果中是多么简单。你能确切地告诉我哪些信息必须出现在搜索结果中吗?

你:在搜索结果的上下文中,图书信息应该包含ISBN、Title和AuthNames。

乔:你能试着写下“守望者”的BookInfo record是什么样子吗?

你:当然可以,给你…

清单3.8 搜索结果上下文中守望者的BookInfo record

{
	"title": "Watchmen", 
	"isbn": "978-1779501127",
	"authorNames": [ "Alan Moore", "Dave Gibbons",
	]
}

乔:现在,我将逐步向您展示如何使用Lodash中的通用数据操作函数编写一个函数,该函数返回与JSON格式的标题匹配的搜索结果。

你:酷!

Joe:让我们从AuthNames()函数开始,该函数通过查看AuthsById索引来计算图书记录的作者姓名。作者姓名的信息路径为[“AuthsById”,AuthId,“Name”]。

清单3.9 计算一本书的作者姓名

function authorNames(catalogData, book) {
	var authorIds = _.get(book, "authorIds");
	var names = _.map(authorIds, function(authorId) {//1
		return _.get(catalogData, ["authorsById", authorId, "name"]);
	});
	return names;
}
  1. 可以使用.forEach()而不是.map()来完成

你:这个_.map()函数是什么?它闻起来像是函数式编程的东西!你答应过我不用学FP就能实现的!

乔:如果你愿意,你可以使用Lodash的_.forEach()。

你:是的,我喜欢。下一个是什么?

乔:现在,我们需要一个将BookInfo记录转换成BookInfo record的bookInfo函数。

清单3.10将BookInfo 记录转换为BookInfo record

function bookInfo(catalogData, book) { 
	var bookInfo = {//1
		"title": _.get(book, "title"),
		"isbn": _.get(book, "isbn"),
		"authorNames": authorNames(catalogData, book)
	};
	return bookInfo;
}
  1. 不需要为bookInfo创建类

你:查看代码,我发现BookInfo记录有三个字段:Title、ISBN和AuthNames。有没有办法在不查看代码的情况下获得这些信息?

乔:您可以将其添加到数据实体图中,也可以将其写入bookInfo函数的文档中,或者两者兼而有之。

你:我必须习惯这样的想法,在DO中,记录字段信息不是程序的一部分。

乔:的确,这不是计划的一部分,但它给了我们很大的灵活性。

你:有没有办法让我两者兼得?!

乔:是的。在第3部分中,我将向您展示如何将record字段信息作为DO程序的一部分。

你:听起来很有趣!

乔:现在,我们已经准备好编写searchBooksByTitle函数,该函数返回与查询匹配的图书信息。首先,我们找到与查询匹配的Book record(使用_.filter()),然后将每个Book record转换为BookInfo record(使用_.map()和bookInfo())。代码如下:

清单3.11搜索与查询匹配的图书

function searchBooksByTitle(catalogData, query) {
	var allBooks = _.get(catalogData, "booksByIsbn");
	var matchingBooks = _.filter(allBooks, function(book) {//1 //2
			return _.get(book, "title").includes(query);
		}
	);

	var bookInfos = _.map(matchingBooks, function(book) { //2
			return bookInfo(catalogData, book);
		}
	);
	return bookInfos;
}

searchBooksByTitle(catalogData, "Watchmen");
  1. 将Map传递给_.filter()时,它会遍历Map的值
  2. 可以使用_.forEach()完成

你:要访问图书记录的标题,你要写_.get(book,“title”),这对我来说有点奇怪。我希望它是点符号的book.title,或者括号符号的book[“title”]!

乔:记住,那本书是一种record,不是用物体来表示的。这是一张Map。实际上,在JavaScript中,您可以编写_.get(book,“title”)、book.title或book[“title”]。但我更喜欢使用Lodash的_.get()。在某些语言中,点和方括号符号在Map上可能不起作用。

你:搜索实现完成了吗?

乔:差不多了。我们编写的searchBooksByTitle函数是Catalog模块的一部分,它返回一个record集合。我们必须编写一个函数,该函数是Library模块的一部分,它返回一个JSON字符串。

你:你早些时候告诉我,在DO中JSON序列化非常简单。

乔:对。以下是searchBooksByTitleJSON()的代码。它检索Catalog记录,将其传递给searchBooksByTitle(),并使用JSON.stringify()将结果转换为JSON()(这是JavaScript的一部分)。

清单3.12 以JSON格式检索图书馆图书

function searchBooksByTitleJSON(libraryData, query) {
	var results = searchBooksByTitle(_.get(libraryData, "catalog"), query); 
	var resultsJSON = JSON.stringify(results);
	return resultsJSON;
}

你:到目前为止,我们将如何组合我们已经编写的四个函数呢?

乔:函数AuthNames、bookInfo和searchBooksByTitle进入Catalog模块,而searchBooksByTitleJSON进入Library模块。

您会看到两个模块的结果代码,对代码的简洁性感到非常惊讶。

清单3.13计算搜索结果。代码分为两个模块:Library 和Catalog。

class Catalog {
	static authorNames(catalogData, book) {
		var authorIds = _.get(book, "authorIds");
		var names = _.map(authorIds, function(authorId) { //1
			return _.get(catalogData, ["authorsById", authorId, "name"]);
		});
		return names;
	}

	static bookInfo(catalogData, book) {
		var bookInfo = { //2
			"title": _.get(book, "title"),
			"isbn": _.get(book, "isbn"),
			"authorNames": Catalog.authorNames(catalogData, book) 
		};
		return bookInfo;
	}

	static searchBooksByTitle(catalogData, query) {
		var allBooks = _.get(catalogData, "booksByIsbn");
		var matchingBooks = _.filter(allBooks, function(book) { //1 //3
			return _.get(book, "title").includes(query);
		});
		var bookInfos = _.map(matchingBooks, function(book) { //1
			return Catalog.bookInfo(catalogData, book);
		});
		return bookInfos;
	}
}

class Library {
	static searchBooksByTitleJSON(libraryData, query) {
		var catalogData = _.get(libraryData, "catalog");
		var results = Catalog.searchBooksByTitle(catalogData, query);
		var resultsJSON = JSON.stringify(results); //4
		return resultsJSON;
	}
}
  1. 可以使用_.forEach()完成
  2. 不需要为bookInfo创建类
  3. 向_.filter()传递映射时,它会遍历映射的值
  4. 将数据转换为JSON(JavaScript的一部分)

你:让我们检查一下代码是否按预期工作。

乔:当然可以。为此,我们需要创建一个包含Catalog record的Library record。

清单3.14 Library 数据(没有用户管理数据)

var libraryData = {
	"name": "The smallest library on earth",
	"address": "Here and now",
	"catalog": {
		"booksByIsbn": {
			"978-1779501127": {
				"isbn": "978-1779501127",
				"title": "Watchmen",
				"publicationYear": 1987,
				"authorIds": ["alan-moore",
					"dave-gibbons"
				],
				"bookItems": [{
						"id": "book-item-1",
						"rackId": "rack-17",
						"isLent": true
					},
					{
						"id": "book-item-2",
						"rackId": "rack-17",
						"isLent": false
					}
				]
			}
		},
		"authorsById": {
			"alan-moore": {
				"name": "Alan Moore",
				"bookIsbns": ["978-1779501127"]
			},
			"dave-gibbons": {
				"name": "Dave Gibbons",
				"bookIsbns": ["978-1779501127"]
			}
		}
	},
	"userManagement": {
		// omitted for now
	}
};

你:让我们搜索书名与“守望者”相匹配的书籍。

清单3.15 JSON中的搜索结果

Library.searchBooksByTitleJSON(libraryData, "Watchmen");
// returns "[{\"title\":\"Watchmen\",\"isbn\":\"978-1779501127\",\"authorNames\":[\"Alan Moore\",
//	\"Dave Gibbons\"]}]"

您将再次查看清单3.12…中的源代码。几秒钟后,你会觉得自己像在一个Aha!时刻。

你:重要的不是代码是否简洁,而是代码不包含抽象。这只是数据操纵!

乔微笑着回答说:“你成功了,我的朋友!”

乔:这让我想起了10年前我的第一位冥想老师告诉我的话:冥想引导头脑去把握现实,而不是我们的思想造成的抽象。

TIP 在DO中,我们的代码库的许多部分往往只是关于数据操作,而不是抽象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
 一: 什么是ECS?       ECS是一种新的架构模式(当然是针对Unity本身来说),这是一个取代GameObject / Component 的模式。 其模式遵循组合优于继承原则,游戏内的每一个基本单元都是一个实体,每个实体又由一个或多个组件构成,每个组件仅仅包含代表其特性的数据(即在组件中没有任何方法)。系统便是来处理拥有一个或多个相同组件的实体集合的工具,其只拥有行为(即在系统中没有任何数据)。       ECS旨在比GameObject / MonoBehaviour更容易处理大量物体。ECS由于是面向数据的设计,所以很容易并行高速处理,以及与Unity开发的C#JobSystem/Burst Compiler一起工作,从而获取更高的项目运行性能。二:ECS总体发展历史       目前Unity公司由Mike Acton 大佬主持DOD(Data Oriented Design 面向数据设计)的全面改革与推进工作,目的是旨在进一步推进Unity系统本身与Unity项目的执行效率。    ECS总体发展:    A: 在Unity2018引入Entities之前,其实ECS框架早已经出现,但是ECS还是因为守望先锋采用了这个框架才被我们所熟知,后来Git上面出现了一个Entitas的插件可以直接用在Unity上面。Entitas-Unity(有多个语言的port版本,Entitas-Unity下统一称为Entitas) 是 Unity的一个ECS(Entity/Component/System)框架,是在github上面的一个开源项目。    B: 经过Unity公司的认可与改造,目前Unity2018.x 版本已经通过 Package Manager 提供了Unity版本的ECS插件,名称为:“Entities”。    C: Unity2019.x 版本,对“Entities”插件的API进行了进一步改造与完善,以更规范的API实现更加高效的与规范的编程模式。 三:ECS(一)轻松入门篇       本“ECS入门篇”旨在全面讲解ecs 的相关理论与简单Hello World 的编程模式,且通过Unity2018.x与Unity2019.x 两个不同API的编程模式,初步讲解ECS的编程规范与模式规范。        需要进一步学习ECS的小伙伴们,可以关注后续“ECS(二)小试牛刀”篇的进一步讲解。   《Unity ECS(二) 小试牛刀》https://edu.csdn.net/course/detail/27096 

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值