接上文
IOC 容器是一个很方便的模块管理工具。
除了可以用来注册和获取模块,IOC 容器一般还会有一个隐藏的功能,即:
注册接口模块
抽象-实现 这种形式注册和获取对象的方式是符合依赖倒置原则的。
依赖倒置原则(Dependence Inversion Principle):程序要依赖于抽象接口,不要依赖于具体实现。
好处如下:
- 接口设计与实现分成两个步骤,接口设计时可以专注于设计,实现时可以专注于实现。
- 接口设计时专注于设计可以减少系统设计时的干扰。
- 实现是可以替换的,比如一个接口叫 IStorage,其实现可以是 PlayerPrefsStorage、EasySaveStorage、EdtiroPrefsStorage,等切换时候只需要一行代码就可以切换了。
- 比较容易测试(单元测试等)
- 当实现细节(比如 PlayerPrefsStorage)发生变化时,其引用接口(比如 IStorage)类里的代码不会跟着改变,降低耦合。
- 等等
支持数据存储
接口的阉割技术
利用 C# 接口的显示实现,来达到接口方法在子类阉割的目的,而要想调用这个方法必须通过接口而不是方法所在的对象。
using UnityEngine;namespace FrameworkDesign.Example
{/// <summary>/// 1. 定义接口/// </summary>public interface ICanSayHello{void SayHello();void SayOther();}public class InterfaceDesignExample : MonoBehaviour,ICanSayHello{/// <summary>/// 接口的隐式实现/// </summary>public void SayHello(){Debug.Log("Hello");}/// <summary>/// 接口的显式实现/// </summary>void ICanSayHello.SayOther(){Debug.Log("Other");}// Start is called before the first frame updatevoid Start(){// 隐式实现的方法可以直接通过对象调用this.SayHello();// 显式实现的接口不能通过对象调用// this.SayOther() // 会报编译错误// 显式实现的接口必须通过接口对象调用(this as ICanSayHello).SayOther();}}
}
// 显式实现的方法必须通过接口对象调用
(this as ICanSayHello).SayOther();
如果要想调用一个对象的显式实现的方法,那么必须要将此对象强制转换成接口对象才能调用,这样就增加了调用显式实现的方法的成本,所以可以理解为这个方法被阉割了。
使用案例 1 接口-抽象类-实现类的结构
第一种就是比较常见的 接口-抽象类-实现类的结构,示例代码如下:
using UnityEngine;namespace FrameworkDesign.Example
{public class InterfaceStructExample : MonoBehaviour{/// <summary>/// 接口/// </summary>public interface ICustomScript{void Start();void Update();void Destroy();}/// <summary>/// 抽象类/// </summary>public abstract class CustomScript : ICustomScript{protected bool mStarted { get; private set; }protected bool mDestroyed { get; private set; }/// <summary>/// 不希望子类访问 Start 方法,因为有可能破坏状态/// </summary>void ICustomScript.Start(){OnStart();mStarted = true;}void ICustomScript.Update(){OnUpdate();}void ICustomScript.Destroy(){OnDestroy();mDestroyed = true;}/// <summary>/// 希望子类实现 OnStart 方法/// </summary>protected abstract void OnStart();protected abstract void OnUpdate();protected abstract void OnDestroy();}/// <summary>/// 由用户扩展的类/// </summary>public class MyScript : CustomScript{protected override void OnStart(){Debug.Log("MyScript:OnStart");}protected override void OnUpdate(){Debug.Log("MyScript:OnUpdate");}protected override void OnDestroy(){Debug.Log("MyScript:OnDestroy");}}/// <summary>/// 测试脚本/// </summary>private void Start(){ICustomScript script = new MyScript();script.Start();script.Update();script.Destroy();}}
}
第二个使用案例 接口 + 扩展时用于限制方法的访问规则
using UnityEngine;namespace FrameworkDesign.Example
{public class CanDoEveryThing {public void DoSomething1(){Debug.Log("DoSomething1");}public void DoSomething2(){Debug.Log("DoSomething2");}public void DoSomething3(){Debug.Log("DoSomething3");}}public interface IHasEveryThing{CanDoEveryThing CanDoEveryThing { get; }}public interface ICanDoSomething1 : IHasEveryThing{}public static class ICanDoSomeThing1Extensions{public static void DoSomething1(this ICanDoSomething1 self){self.CanDoEveryThing.DoSomething1();}}public interface ICanDoSomething2 : IHasEveryThing{}public static class ICanDoSomeThing2Extensions{public static void DoSomething2(this ICanDoSomething2 self){self.CanDoEveryThing.DoSomething2();}}public interface ICanDoSomething3 : IHasEveryThing{}public static class ICanDoSomeThing3Extensions{public static void DoSomething3(this ICanDoSomething3 self){self.CanDoEveryThing.DoSomething3();}}public class InterfaceRuleExample : MonoBehaviour{public class OnlyCanDo1 : ICanDoSomething1{CanDoEveryThing IHasEveryThing.CanDoEveryThing { get; } = new CanDoEveryThing();}public class OnlyCanDo23 : ICanDoSomething2,ICanDoSomething3{CanDoEveryThing IHasEveryThing.CanDoEveryThing { get; } = new CanDoEveryThing();}private void Start(){var onlyCanDo1 = new OnlyCanDo1();// 可以调用 DoSomething1onlyCanDo1.DoSomething1();// 不能调用 DoSomething2 和 3 会报编译错误// onlyCanDo1.DoSomething2();// onlyCanDo1.DoSomething3();var onlyCanDo23 = new OnlyCanDo23();// 不可以调用 DoSomething1 会报编译错误// onlyCanDo23.DoSomething1();// 可以调用 DoSomething2 和 3onlyCanDo23.DoSomething2();onlyCanDo23.DoSomething3();}}
}
总结一下:
- 接口的阉割技术核心的知识是接口的显式实现
- 接口+抽象类+具体类 时,不想被乱调用一些方法时可以用接口阉割技术。
- 接口+静态扩展时,想要通过实现某个接口来获得具体方法的访问权限时可以用接口阉割技术。