作为一个编程相关的游戏,自然少不了造轮子和用轮子,这几年也接触了很多广为流传的轮子和框架,其中不乏一些拥有“革命性”理念的设计,我也从中吸取了许多的经验。但是大家可以发现一个奇怪的现象:广为流传的轮子大多相当基础。
我们经常可以找到一些基础的轮子:例如布局规划、寻路、建筑缓存等,但是对于高级轮子,例如 lab 合成,factory 合成,乃至更高级的战争模块,无一例外都找不到可以用的轮子。这朵乌云一直徘徊在 screeps 的大厦上,很大程度的破坏了那些想要把精力聚焦到某个具体模块的玩家体验。
接下来,我们就分析一下为什么会这样,以及如何解决这个问题,让大家可以从只能各自为战的泥潭中脱身出来。
为什么没有出现高级轮子?
问题的根源跟简单:越高级的模块的依赖就越多。我有一个 factory 模块,它想要工作的话需要一个物流模块、一个跨房资源共享模块,一个 power 管理模块等等...一堆模块相互依赖,大家共同组成了一个不断蠕动的肉球,其中相互依赖的血管盘根错节看不清楚,想要把工厂模块单独拿出来给其他人用?洗洗睡吧您。
这里可以套用 JoeArmstrong 的那句话:
你想要个香蕉,但拿到的却是拿着香蕉的猩猩,乃至最后你拥有了整片丛林。
你想复用一个 lab 模块,但是你需要同时把房间物流模块也一块装进来,层层嵌套之后,你发现几乎把原来项目一半的代码都搞了过来。随着模块的层级越高,你需要引入的依赖模块就越多。并且你还要避免新引入的猩猩和原来自己写的猩猩打架。
怎么解决?避免直接依赖
现在我们知道了问题的根源,那想解决这个问题也很简单:把模块之间的依赖关系删了不就好了。
// 就是这种引入代码,不可以出现在工厂模块里
import { getGlobalResourceShare } from 'src/module/resourceShare'
说得轻巧,删?怎么删,工厂模块就是需要物流模块啊,你不让我写引入,那我咋搞,嘴里含根内存条靠脑补?
你说的没错,依赖关系不可以被消除,但是可以被转移。我们只是不想让他出现在模块内部,只要能想办法把这些引入代码搞出去就可以了。
首先看一下我们原本的设计,每个模块相互独立并调用其他模块的接口完成自己的功能,因为彼此之间牵一发而动全身的依赖关系,导致了我们无法单独复用某个模块:
但是如果我们通过某种方式“隔离”了其他的接口,让本模块调用的“外部模块操作”是这个隔离层上暴露的接口,这样模块内部的实现就只依赖于这个隔离层,从而保证模块 内部 的纯洁性。
然后,我们就可以通过在“提供”和“依赖”之间相互传递,来实现模块之间的相互协作。
这时候我们可以发现,所有“脏乱差”的依赖关系从模块之间沉淀到了一起,由于在这一层里 所有的代码都是为了让不同模块彼此合作。为了方便称呼,我们就可以他称之为“胶水层”或正式一点的“组合层”。
至此,我们就把整个应用分为干净的“模块层”和干脏活的“胶水层”。可以看到,原来那些依赖关系依旧存在,但是从 模块与模块之间 转移到了 模块之外。
现在,我们就可以简单的把某个模块拿出来、替换成新的模块,然后修改一下胶水层里的依赖关系就可以了。整个替换过程里完全不需要侵入到其他模块内部。
我们可以把每个功能模块想象成电脑里的内存、电源、硬盘、显卡。我们要开发的 screeps bot 就像是在组装一台电脑,每个模块都不能单独工作,但是他们都提供了各种接口和插槽,我们只需要按照说明书把他们用各种线缆连接在一起,电脑就能正常工作了!
这里的各种线缆和插口就是上文中提到的“胶水层”,想更换一个显卡,只需要把与之相关的插口检查一遍就行,不用把 CPU 开盖或者在主板上搞什么飞线(不用侵入其他模块进行修改)。
小结
本篇文章里,我们介绍了为什么没办法进行高级轮子的复用,以及提出了一个方案来尝试解决这个问题:通过给每个模块设定好外部依赖的接口,让模块对其他模块的依赖转移成对自己接口的依赖,从而保证模块内部和其他模块是相互隔离的。
这里我们需要明确一个观点,不需要任何配置,即插即用的高级轮子是不存在的。越高级的轮子依赖的东西就越多,当我们想要独立一个轮子时,这些依赖关系必定会暴露出来,不提供这些依赖,那你就用不了这个轮子,这和“不需要任何配置”是相违背的。
其实上面的设计给你俩小时你也想得出来。关键就是如何将其落实到代码上面。这一部分内容我们会在 下篇文章 里详细介绍,不用担心,非常简单。
网友评论