第一原则
在本篇中主要介绍数据和代码分离这一概念。此概念属于面向数据程序设计(DO),就是要打破面向对象设计(OO)中关于
封装
(将代码和数据都放入一个对象中)的概念,将有状态类(
Stateful class
)转换成无状态类(Stateless class),从而增强代码复用性或者解耦。
分离的好处
-
增加代码在不同上下文界限(Context)中的复用性
-
越独立的代码越容易测试
-
降低系统的复杂性
好处1.增加代码复用性
DO如何复用代码
在OO中,通过继承和组合来复用代码。当继承与组合越多,系统变得更加复杂。
OO如何复用代码
在DO中,以C#为例,我们将数据放入数据块类中,方法整合进静态类中,然后将数据块作为方法参数传入,如下所示。
public
class
PlayerMoveData
{
public
Vector2
velocity
;
public
float
jumpForce
;
}
public
static
class
PlayerMoveModule
{
public
static
void
PlayerMove
(
PlayerMoveData
data
)
{
//...
}
public
static
void
PlayerJump
(
PlayerMoveData
data
)
{
//...
}
}
这个代码片段目前没有太大意义,因为缺少重要的上下文界限(Unity环境),但有助于理解分离的概念。在接下来的篇章中,我会详细阐述这些代码在Unity中是怎样运行的。
好处2.更加容易测试的代码
现在假定我们有一个需求,玩家速度越大弹跳力越高,需要测试: 当velocity=(0,10)时, jumpForce = 5 ? 为此,编写如下测试代码。
public
void
VelocityInfluenceJumpforceTest_velocity10_jumpForce5
(
)
{
//Arrange
var
data
=
new
PlayerMoveData
(
)
;
//Act
PlayerMoveModule
.
PlayerMove
(
data
)
;
//Assert
expected
=
5
;
actual
=
data
.
jumpForce
;
Assert
.
AreEqual
(
expected
,
actual
)
;
}
如果将代码和数据都写入Player脚本中,并继承Mono类。因为不能实例化Mono类,测试难以进行。
好处3.代码更加易懂但却有代价
虽然面向数据有其好处,但却有不可忽视的代价:
-
数据对所有代码来说都是可见的
-
性能问题。面向数据认为数据是不可变的,每次对数据的修改只是创造了一个拷贝,但是这样会造成大量无用的旧数据沉积。在上面的例子中,我没有做拷贝操作,而是直接修改,可能会造成数据竞争问题。但是我认为这种可能性很小,因为Unity是单线程环境,造成数据竞争的概率很小。
-
数据竞争(Data race),两个不同代码片段同时修改同一个数据,造成数据竞争。
参考资料 Yehonathan Sharvit, Data-Oriented Programming
本文探讨了数据和代码分离在DO中的应用,强调了如何通过这种方式增强代码复用性和测试性,避免继承和组合带来的复杂性。通过C#示例,展示了如何将数据和行为分开,以及它在Unity中的实践。同时,讨论了可能的数据竞争问题和性能影响。

被折叠的 条评论
为什么被折叠?



