(二)设计模式之游戏–依赖倒置原则
引入问题:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。
解决方案:将类A修改为依赖抽象I,类B和类C各自实现抽象I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率,实际上就是解耦合。
(1)定义:
-
高层模块不应该依赖底层模块,他们都依赖于抽象(接口,抽象类等),抽象不应该依赖于细节,细节应该依赖于抽象。
-
要针对接口编程,不要针对实现编程。
(2)实现分析:
a) 代码要依赖于抽象的类,而不要依赖于具体的类,高层模块不依赖于底层的模块,要针对接口编程而不针对实现或者是具体类编程。
b) 实现依赖倒转原则常用的方式是在代码中使用抽象类或接口。
c) 依赖倒转原则要求客户端依赖于抽象耦合,以抽象的方式耦合是依赖倒转的关键
d) 依赖注入:
通过构造函数注入实例变量。
通过Setter方法注入实例变量。
通过 接口方法注入实例变量。
(3) 示例:
在游戏中经常会出现让玩家攻击各种怪物这个功能。
原始方案如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour {
void Start () {
Attack(new SoliderMonster());
}
void Attack(SoliderMonster soliderMonster)
{
soliderMonster.TakeAttack();
}
}
public class SoliderMonster
{
public void TakeAttack()
{
Debug.Log("SoliderMonster被攻击");
}
}
看起来每什么问题,但当我们改为攻击其它类型的怪物时,我们就需要修改高层模块Attack方法,以适合我们的需求,这显然不符合我们的要求。根据引入问题的解决方案,我们可以一个怪物抽象类(Monster)然后,让各种怪物继承它,并实行该抽象,然后再让人物依赖该抽象。重构如下。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour {
private Monster monster;
void Start () {
Setter(new MasterMonster());
Attack();
Setter(new SoliderMonster());
Attack();
}
public void Setter(Monster target)
{
monster = target;
}
void Attack()
{
monster.TakeAttack();
}
}
public abstract class Monster
{
public abstract void TakeAttack();
}
public class SoliderMonster: Monster
{
public override void TakeAttack()
{
Debug.Log("SoliderMonster被攻击");
}
}
public class MasterMonster: Monster
{
public override void TakeAttack()
{
Debug.Log("MasterMonster被攻击");
}
}
现在当新增怪物时,我们只需让我们新增的具体怪物类继承自抽象怪物类即可,玩家类基本上不用改变。
其它依赖注入方式:
上面我们使用的是通过Setter方法注入实例变量。当然还有构造器注入、接口方法注入。
接口方法注入实例变量:
void Attack(Monster monster)
{
monster.TakeAttack();
}
通过构造函数注入实例变量,值得注意的是Unity继承自MonoBehaviour的类不能使用new 关键字通过构造方法来构造。
public Player(Monster target)
{
monster = target;
}