可以想象,以上每个方法都应该返回符合条件的节点集合 。这种实现是很直观的,不过存在一个潜在的问题:很多时候用户只需要一小部分结果,如果它总是不计代价地给我们一个巨大的集合,会造成极大的浪费 。比如以下查询:
为了避免不必要的浪费,我们需要另外一种机制,也就是通常所称的“懒式查询”或“延迟查询” 。它的基本思想是 , 当我们调用查询方法时,它只是把查询条件记录下来,而并不立即返回结果,直到明确调用某些方法时才真正去查询数据库 。
如果读者比较熟悉流行的Python ORM , 比如 SqlAlchemy 或者 Django ORM 的话,会知道它们几乎都是懒式查询的,要调用 list(result) 或者 result[0:10] 这样的方法才能得到具体的查询结果 。
在Dagoba 中把触发查询的方法定义为 run。也就是说,以下查询执行到 run 时才真正去查找数据:
和懒式查询( Lazy Query )相对应的,直接返回结果的方法一般称作主动查询( Eager Query ) 。主动查询和懒式查询的内在查找逻辑基本上是相同的,区别只在于触发机制不同 。由于主动查询实现起来更加简单,出错也更容易排查,因此我们先从主动查询开始实现 。
还是从测试开始 。前面测试所用的简单数据库数据太少,难以满足查询要求 , 所以这一步先来创建一个更复杂的数据模型:
此关系的复杂之处之一在于反向关联:如果 A 是 B 的哥哥,那么 B 就是 A 的弟弟/妹妹,为了查询到他们彼此之间的关系,正向关联和反向关联都需要存在,因此在初始化数据库时需要定义的边数量会很多 。
当然 , 父子之间也存在反向关联的问题,为了让问题稍微简化一些 , 我们目前只需要向下(子孙辈)查找,可以稍微减少一些关联数量 。
因此,我们定义数据模型如下 。为了减少重复工作,我们通过_backward 字段定义反向关联,而数据库内部为了查询方便 , 需要把它维护成两条边:
然后,测试一个最简单的查询 , 比如查找某人的所有孙辈:
这里outcome/income 分别表示从某个节点出发、或到达它的节点集合 。在原作者的代码中把上述方法称为 out/in。当然这样看起来更加简洁,可惜的是 in 在 Python 中是个关键字,无法作为函数名 。我也考虑过加个下划线比如 out_.in_ 这种形式,但看起来也有点怪异,权衡之后还是使用了稍微啰嗦一点的名称 。
现在我们可以开始定义查询接口了 。在前面已经说过,我们计划分别实现两种查询,包括主动查询( Eager Query )以及延迟查询( Lazy Query ) 。
它们的内在查询逻辑是相通的,看起来似乎可以使用继承 。不过遵循YAGNI 原则,目前先不这样做,而是只定义两个新类 , 在满足测试的基础上不断扩展 。以后我们会看到,与继承相比,把共同的逻辑放到数据库本身其实是更为合理的 。
接下来实现访问节点的方法 。由于EagerQuery 调用查询方法会立即返回结果 , 我们把结果记录在 _result 内部字段中 。虽然 node 方法只返回单个结果,但考虑到其他查询方法几乎都是返回集合 , 为统一起见,让它也返回集合 , 这样可以避免同时支持集合与单结果的分支处理,让代码更加简洁、不容易出错 。此外,如果查询对象不存在的话,我们只返回空集合,并不视为一个错误 。
查询输入/输出节点的方法实现类似这样:
查找节点的核心逻辑在数据库本身定义:
以上使用了内部定义的一些辅助查询方法 。用类似的逻辑再定义income , 它们的实现都很简单 , 读者可以直接参考源码,此处不再赘述 。
推荐阅读
- 看微信直播骗术,看微信直播骗术揭秘
- 还需不需要学jquery,学了jquery有必要学vue吗
- h3c路由器怎么查看负载,h3c查看端口负载
- 小学生三年级益智游戏大全,适合小学三年级玩的益智游戏
- vb.net的数据类型 vb6数据类型
- linux命令行有空格,命令行路径有空格
- 损坏酒店电视怎么处理,弄坏酒店电视机怎么办
- 红运直播录屏怎么录视频,红运直播录屏怎么录视频教程
- python数学函数库 python math函数库