对象之间的交互有三种,它们是:

  • 方法
  • 委托
  • 事件

其实还有一种我们后边学习的:命令模式

在第一季时我们引出对象之间的交互,只是直接给出了结论,并没有介绍对象之间的交互这个概念是如何发展起来的。在这里我们着重介绍。

最开始并没有提出对象之间的交互这个概念,而是从“依赖”这个角度逐步发展出来的。

我们先看一下单向依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
namespace FrameworkDesign2021.ObjInteractExample
{
public class A
{
public void Say()
{
Debug.Log("Hello I'm A");
}
}
public class B
{
public B()
{
new A().Say();
}
}
}

在B的构造中,访问了A的Say方法。这种交互是单向的(B——>A),即B 单向访问/引用/依赖/调用 A,总之叫啥都行,我们就用单向依赖来描述这种交互。

单向依赖

代码中出现了单向依赖,在一般情况下不会出现什么问题,产生单向依赖是正常的,代码也是合格的。

我们先说一下最佳的单向依赖是什么样的。

最佳的单向依赖

就像我们第一季提到过的,最佳的单向依赖就是自顶向下的调用。

在UI系统中我们在Panel(父脚本)通过Button(子控件)添加onClick监听。

再比如GameManager(顶端模块)调用LevelManager(子模块)的方法。

这些都是单向依赖的例子,如果能够把对象交互控制在自顶向下的单项依赖,那么代码会变得更有条理、清晰。

最差的单向依赖

很简单,反过来自底向上的调用就是差的单向依赖。

例如:

  • Button(子脚本)调用Panel(父脚本)的方法
  • 轮胎告诉汽车,让汽车前进

为什么自底向上的调用是最差的单向依赖呢?

答案很简单,如果父节点为了对子节点的调用提供支持,那么父节点会随着子节点的增多,父节点提供的方法会越来越多,从而造成“头大身小”的畸形结构。

我们知道,在生活中,我们的Leader是需要把责任和压力分给每个成员身上的,面向对象的世界里也是同样的道理,父节点是要把对象的职责拆分给子节点,每个子节点自己负责自己的事情。这样,一个项目的内部才会运转良好,而且结构也会非常清晰。

但如果是自底向上的调用,也就是子节点调用父节点,子节点把一部分职责分给父节点,那么本应该作为控制中心的父节点变成了到处打杂,对象的职责就乱了套。

在代码中有自底向上调用的情况,要尽量避免。

同级之间的单向依赖

同级之间的单向依赖非常简单,我们用图示

同级之间的单向依赖

B调用C,C调用D。

这种情况比自顶向上会好一点,但是风险也是很大的。

如果B想获取到C对象,那么肯定会做一些操作,假如在Unity中,可能会这样写:

1
2
3
4
5
6
7
8
9
public class B:MonoBehaviour
{
void Start()
{
var c = GameObject.Find("C").GetComponent<C>;
var c1 = FindObjectOfType<C>();
//...
}
}

风险很明显,不管是GameObject.Find还是FindObjectOfType,这两个API会提高项目的维护成本。

Transform.Find由于指向性强,是可以使用的,而且只有Transform.Find可以获取到没有激活的游戏对象

GameObject.Find更推荐GameObject.FindWithTag,详情请看官方的API文档。

单向依赖的两种实现

自顶向下的单向依赖实现

自顶向下的单向依赖实现非常简单,最简单的方式就是父节点持有子节点的引用。

1
2
3
4
5
6
7
8
9
10
11
namespace FrameworkDesign2021.Example
{
public class Parent
{
Child mChild = new Child();
}
public class Child
{

}
}

我们可能天天在写这种代码,只不过,在这里我们给了它这种概念。

这种实现是相对来说比较具体的实现。

除了这种自顶向下的单向依赖,还有其他的方式。

比如顶层模块调用底层模块。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
namespace FrameworkDesign2021.Example
{
public class UIModule : MonoSingleton<UIModule>
{
void Start()
{
ShopModule.Instance.Init();
}
}
public class ShopModule : MonoSingleton<ShopModule>
{
public void Init()
{

}
}
}

到这里,我们知道,对象之间交互的最佳实践(自顶向下的单向依赖),也同样适用于模块之间的交互。

所以本质上,设计模块与设计对象所需要遵循的设计原则是一样的。

示意图

一个良好的对象设计,符合自顶向下依赖的原则。

图中包含了,对象之间的依赖与模块之间的依赖。

模块设计单向依赖示意图