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

2.5 DO系统易于理解

  您可以查看代表系统高级设计的两个图:

  1. 图2.7中的数据思维导图中的数据实体
  2. 图2.8模块图中的代码模块

有点困惑,你问乔:

你:我不确定这个系统是否比传统的OO系统好,在传统的OO系统中,对象封装数据。

乔:与传统的OO系统相比,DO系统的主要好处是更容易理解。

你:是什么让它更容易理解?

乔:系统清晰地划分为代码模块和数据实体。

你:我不明白你的意思。

乔:当您尝试理解系统的数据实体时,您不必考虑操作数据实体的代码的细节。

你:你的意思是,当我看着我的图书馆管理系统的数据思维导图时,我可以自己理解它?

乔:没错。同样,当您尝试理解系统的代码模块时,您不必考虑代码操作的数据实体的细节。代码和数据之间存在明显的关注点分离。

再次查看图2.7中的数据思维导图,您会有一种恍然大悟的感觉:

图2.7 图书馆管理系统数据思维导图

 

重点 DO系统更容易理解,因为系统分为两部分:数据实体和代码模块。

现在,您看到图2.8中的模块关系图,您会感到有点困惑:

  • 一方面,模块图看起来类似于经典OO中的类图:用于类的方框和用于类之间关系的箭头。
  • 另一方面,代码模块图看起来比经典OO中的类图简单得多,但是您无法解释原因。

你要乔澄清一下。

你:模块图似乎比我在OO中习惯的类图简单得多。我能感觉到,但我说不出来。

乔:原因是模块图有约束。

你:什么样的约束?

乔:正如我们之前看到的,对函数的约束:所有函数都是静态的(无状态)。而且还限制了模块之间的关系。

TIP DO模块中的所有函数都是无状态的。

你:你能解释一下吗?

乔:DO模块之间只有一种关系:使用关系。一个模块使用来自另一个模块的代码。模块之间没有关联,没有组合,也没有继承。这就是使DO模块图易于理解的原因。

你:我理解为什么DO模块之间没有关联,也没有组合:毕竟,关联和组合是数据关系。但是为什么没有继承关系呢?这是否意味着在DO中是反对多态性的?

乔:这是个很好的问题。简单的答案是,在DO中,我们使用与类继承不同的机制实现多态性。我们将在后面讨论这个问题(在第5章)。

你:现在,你引发了我的好奇心:我很确定继承是实现多态性的唯一途径。

TIP DO模块之间唯一的关系是:使用关系。

  再次查看图2.8中的模块关系图,现在您不仅感觉到该关系图比经典的OO类关系图更简单,而且还理解了为什么它更简单:所有函数都是静态的,并且模块之间的所有关系都是使用类型的。

图2.8带函数参数的图书馆管理系统模块

 

表2.1 是什么使DO系统的每个部分易于理解

系统部分对实体的约束对关系的约束
数据实体仅限成员(无代码)联合与组合
代码模块无状态函数(无成员)使用(无继承)

TIP DO系统的每个部分都很容易理解,因为它有约束。

2.6 DO系统非常灵活

你:我知道代码和数据之间的明显分离使得DO系统比传统的面向对象系统更容易理解。但是如何适应需求的变化呢?

乔:DO系统的另一个好处是很容易使它们适应不断变化的需求。

您:我记得当Nancy要求我向系统中添加超级成员和VIP成员时,很难调整我的OO系统:我必须引入几个基类,类层次结构变得非常复杂。

乔:我完全知道你在说什么。我在做面向对象开发人员时也经历过同样的挣扎。告诉我对超级会员和VIP会员的要求有哪些变化,我相信你自己就会看到,调整你的DO系统很容易。

旁白
对超级会员和VIP会员的要求

  1. 超级会员是指允许列出其他会员的图书借阅情况的会员
  2. VIP会员是指允许向图书馆添加图书项目的会员

  您打开IDE,开始编写Library模块的getBookLendings函数,首先没有解决超级成员的要求。您还记得乔在DO:中告诉您的关于模块函数的内容吗?

  1. 函数是无状态的
  2. 函数接收它们操作的数据作为第一个参数

在功能方面,getBookLendings分为两部分:

  1. 检查用户是否为图书管理员
  2. 从目录中检索借阅图书

基本上,getBookLendings的代码由两部分组成:

  1. 从UserManagement模块调用isLibrarian()函数并将UserManagementData传递给它
  2. 从Catalog模块调用getBookLendings()函数并将CatalogData传递给它

以下是Library.getBookLendings()的代码:

清单2.1获取会员的图书借阅量

class Library {
	static getBookLendings(libraryData, userId, memberId) { 
		if(UserManagement.isLibrarian(libraryData.userManagement, userId)) {
			return Catalog.getBookLendings(libraryData.catalog, memberId);
		} else {
			throw "Not allowed to get book lendings"; //1
		}
	}
}

class UserManagement {
	static isLibrarian(userManagementData, userId) {
		// will be implemented later //2
	}
}

class Catalog {
	static getBookLendings(catalogData, memberId) {
		// will be implemented later  //3
	}
}
  1. 还有其他方法可以管理错误
  2. 在第3章中,我们将了解如何使用泛型数据集合管理权限
  3. 在第3章中,我们将了解如何使用泛型数据集合查询数据

  这是您的第一段DO代码:传递所有这些数据对象LibraryData、LibraryData.userManagement和LibraryData.Catalog感觉有点笨拙。但你做到了。
  乔看了看您的代码,似乎很满意。

乔:你会如何调整你的代码以适应超级会员?

您:我会将函数isSuperMember添加到UserManagement模块,并像这样Library.getBookLendings调用它

乔:没错!就这么简单。

你在你的笔记本电脑上输入这段代码:

清单2.2 允许超级会员获得会员的图书外借

class Library {
	static getBookLendings(libraryData, userId, memberId) { 
		if(Usermanagement.isLibrarian(libraryData.userManagement, userId) ||
		Usermanagement.isSuperMember(libraryData.userManagement, userId)) { 
			return Catalog.getBookLendings(libraryData.catalog, memberId);
		} else {
			throw "Not allowed to get book lendings"; //1
		}
	}
}

class UserManagement {
	static isLibrarian(userManagementData, userId) {
		// will be implemented later //2
	}
	static isSuperMember(userManagementData, userId) {
		// will be implemented later //2
	}
}

class Catalog {
	static getBookLendings(catalogData, memberId) {
		// will be implemented later //3
	}
}
  1. 还有其他方法可以管理错误
  2. 在第3章中,我们将了解如何使用泛型数据集合管理权限
  3. 在第3章中,我们将了解如何使用泛型数据集合查询数据

  现在,传递所有这些数据对象所造成的尴尬感觉主要是一种解脱的感觉:适应这种需求变化只需要几行代码,不需要对系统设计进行任何更改。

  乔又一次看起来很满意。

TIP DO系统是灵活的。通常,它们在不改变系统设计的情况下适应不断变化的需求。

  您为自己准备了一杯咖啡,然后开始编写addBookItem()代码

  查看清单2.3中Library.addBookItem()的签名,您不清楚第三个参数bookItemInfo的含义。你要乔澄清一下。

清单2.3 Library.addBookItem的签名

class Library {
	static addBookItem(libraryData, userId, bookItemInfo) {
	}
}

你:boItemInfo是什么?

乔:让我们将其称为图书项目信息,并设想我们有一种方法可以在名为bookItemInfo的数据实体中表示该信息。

你:你是说一个对象?

乔:目前,把bookItemInfo看作一个对象是可以的。稍后(在第3章),我将向您展示如何在DO中表示数据。

  除了这个关于图书项目信息如何由bookItemInfo表示的细微差别之外,清单2.4中Library.addBookItem()的代码与您在清单2.2中为Library.getBookLendings()编写的代码非常相似。再一次让您惊讶的是,添加对VIP成员的支持不需要更改设计。

清单2.4允许VIP成员向图书馆添加图书项目

class Library {
	static addBookItem(libraryData, userId, bookItemData) { 
		if(UserManagement.isLibrarian(libraryData.userManagement, userId) ||
		UserManagement.isVIPMember(libraryData.userManagement, userId)) { 
			return Catalog.addBookItem(libraryData.catalog, bookItemData);
		} else {
			throw "Not allowed to add a book item"; //1
		}
	}
}

class UserManagement {
	static isLibrarian(userManagementData, userId) {
		// will be implemented later //2
	}
	static isVIPMember(userManagementData, userId) {
		// will be implemented later //2
	}
}

class Catalog {
	static addBookItem(catalogData, memberId) {
		// will be implemented later //3
	}
}
  1. 还有其他方法可以管理错误
  2. 在第3章中,我们将了解如何使用泛型数据集合管理权限
  3. 在第3章中,我们将了解如何使用泛型数据集合查询数据

你:我需要一个很大的心态转变来学习如何将代码从数据中分离出来。

乔:对你的头脑来说,最具挑战性的部分是什么?

你:数据没有封装在对象中的事实。

乔:我从OO转到DO的时候也是一样。

你:在我进入DO的旅程中,还会有其他的心态转变吗?

乔:还会有两次思维方式的转变,但我认为这比把代码从数据中分离出来更不具挑战性。

你:是关于什么的?

乔:用泛型数据结构表示数据实体(第3章),并将我们自己约束到不可变的数据对象(第4章)。

但在此之前,你和乔在你办公室附近的一家不错的小餐馆Simple吃午饭。

2.7 总结

  在本章中,我们已经说明了关于代码与数据分离的DO原则#1:

NOTE 原则#1:以代码驻留在函数中的方式将代码与数据分开,这些函数的行为不依赖于以某种方式封装在函数上下文中的数据。

  在DO中学习这一点需要相当大的心态转变:

  • 代码与数据是分开的
  • 代码在模块中聚合
  • 数据在数据实体中聚合
  • 代码由无状态函数组成
  • 函数接收数据作为第一个参数

  我们演示了如何在面向对象语言中应用这一原则。

这种分离的后果是:

  • 我们可以自由地独立设计代码和数据
  • 模块图很简单:它只与用法有关(没有继承)
  • 数据实体图很简单:它只涉及关联和组合

第一条原则的细节总结在这个思维导图中:

图2.9原则#1:将代码与数据分开

 
  总体而言,DO系统比传统的OO系统更简单(更容易理解),也更灵活(更容易适应不断变化的需求)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值