层级关系
最近我一直在思考一个问题,就是究竟如何才能写出优雅、健壮的代码!?
最近我终于有了想法。
我了解过面向对象的编程思想,那是一种用于描述客观世界的模型。
简单来说,世间万物都可以通过一些共性来划分成一个个类。
所谓共性,就是拥有一些相同的属性和方法。
属性用于描述事物的特征;方法用于描述事物的行为。
举例来说,学生是一个类,因为学生都有相同的属性,如:学号、所在的学校...
也有相同的方法,如:学习、考试...
但是学术类并不表示任何真实存在的事物,它只是对一些客观事物的思维抽象。
于此对应的是一个个真实存在的个体,我们称之为对象。
每个类的对象不是完全相同的,也不可能完全相同。
世界上没有两片完全相同的叶子,就算对象的属性和方法完全相同,在内存层面也不会是同一个地方,
就像new Object() == new Object()只能是false。
同时类还有很多特性:封装、继承、多态,
其实这些不是本文的重点,我只是想说类的确是一种优秀的编程思想。
但是在实际编程中,总是能遇到很多决策性的代码架构。
比如在一个游戏项目中,有一个物品类、有一个玩家类。
显然玩家可以使用物品,那么使用物品的效果的代码应该放在物品类中还是放在玩家类中。
仔细分析来看,“使用”是玩家的方法;但是物品“使用造成的效果”应该是方法还是属性?
如果是属性,我们可以创建一个效果类,用一个字段来保存物品的效果。然后玩家在使用方法中,switch到对应的效果,执行相应代码。
只是一种比较理性的架构,但是会造成代码量增加,因为要额外维护一个效果类,
有一种简单的想法是,把物品的效果视作一种方法,也就是一个以玩家为参数的函数,这样就可以很方便的在物品对象中管理自己的效果。
我粗略地估计,前者适合大型游戏,可以实现更复杂的物品效果,还可以很方便的把所有物品进行序列化,因为所有属性都可以用简单的键值对表示。
后者适合逻辑简单的游戏,因为使用效果的函数不应该有太多的参数,并且不能序列化也不算一个缺点。
第二个例子问题是,物品类和地点类的关系如何表示,应该在物品中储存物品所属的地点还是应该在地点储存拥有的资源?
二者逻辑上是等价的,每种方法都可以达到建立关系的目的。但是把物品集合作为地点的资源属性,应该是更好的选择。
首先,地点有某物,更符合自觉逻辑,似乎地点与物资的关系也比物品的所属地的关系更紧密。
第二,代码对这种关系的使用,主要就是检索某地有哪些物资,如果是另一种方法则要遍历所有物品对象。
这两个问题解决了。仔细分析还像并不是太难,但也让我考虑的许久。
那么我再来一个问题,在游戏主要逻辑控制代码中,是否应该运行直接控制UI视图的变化?
很多人的答案是肯定的,因为这样的操作很符合逻辑,就是很简单的代码操作元素的行为,有什么不可呢?
我曾经也是如此认为,直到我数个小时的沉思后,我得到的答案是不应该让游戏逻辑控制代码直接操作视图更新!
这一切都要从分层思想说起。先用一句话总结我想到的道理:上层层直接控制下层、下层为上层提供服务。
什么是层?什么又是上层和下层?简单来说,层就是一群有类似行为的类,用以完成某个共通的目标。
简单来说,层也是一种类,上层下层可以理解为上级下级。
如何分别两个层哪个是上级,就看谁对谁有控制作用,哪个是下级,就看谁为谁提供服务。
如果A对B有权力控制,那么A就是B的上级,同时也意味着B为A提供服务。
“有权控制"和"为...提供服务"是一件事情的两种说法。
比如说人"有权控制"锄头 等价于 锄头为人"提供服务",也就说明人是上级,工具是下级。
值得一提的是,”控制“和“为...提供服务”的关系应该是逐级的,
比如人的意识控制人的双手,人的双手控制斧头,但是人的意识不能直接控制斧头。同样的斧头也不直接为意识服务。
在代码层面,上层对下层的控制,意味着就是上层有权访问和调用下层中的对象属性和方法。
在一个组织架构中亦是如此,上级可以调查和调控下级的情况和行为,下级则为上级提供服务。
如果用箭头表示控制链,那么上下级的关系可以简单的表示为:A -> B -> C ->D。
一个良好的下级要为上级提供透明的服务,使得上级完全不用关心下级是如何实现的。
一个良好的下级只负责这就职责以内的事情,不能也不关心上层的方法。
这种上下级的组织架构是一种良好的规范制度。体现了一种程序正义,在理想层面可以使系统非常健壮。
就算某个层级出现了错误也可以不会破坏其他层级。
回到我们的问题,我们首先要确定一下游戏逻辑层和UI层的上下层关系。
显然,整个游戏的目的就是为了渲染出游戏画面,游戏逻辑层为UI层提供服务,游戏逻辑层是下层,UI层是上层。
为什么会认为逻辑层可以控制UI层呢,因为逻辑层更具有主动地位,而且操作上修改UI是允许的。
但基于层级理论,是要实现逻辑层不直接控制UI层。
如果下层确实需要上级的一些操作怎么办呢?我想到有两种方法,一是消息订阅发布机制,另一个响应式数据。
这让我想到生物选必一的反馈机制,就是下级通过某些反馈的途径调控上级的行为。
消息订阅发布机制是一种很好的途径,在实现上事件的订阅和发布全体是共享的,即各层都可以监听事件,并做出各自的相应。
这里更明确的用途用下级向上级做出反馈,即上级订阅事件,下级发布事件。
这些事件不必像函数那样传递许多数据作为参数,一般只要明确通道名即可。
剩下的交给上级操作,因为上级对下级本身就有访问权力。
所以我的建议是逻辑层在视图可能需要更新时发布事件,这主要基于逻辑层级本身的行为。
在上级监听事件,按需处理不同的事件,对应的做出更新。
第二种是一种更加便捷的方式。让上层在需要更新的时候自动更新视图,这样逻辑层就不用时刻关系视图更新了。
这种方案把累活交给视图层来做,由此可以看出顶级并不意味着是最主要的部分,逻辑层才是更关键的。