接上文
IOC 容器是一个很方便的模块管理工具。
除了可以用来注册和获取模块,IOC 容器一般还会有一个隐藏的功能,即:
抽象-实现 这种形式注册和获取对象的方式是符合依赖倒置原则的。
依赖倒置原则(Dependence Inversion Principle):程序要依赖于抽象接口,不要依赖于具体实现。
好处如下:
利用 C# 接口的显示实现,来达到接口方法在子类阉割的目的,而要想调用这个方法必须通过接口而不是方法所在的对象。
using UnityEngine;
namespace FrameworkDesign.Example
{
///
/// 1. 定义接口
///
public interface ICanSayHello
{
void SayHello();
void SayOther();
}
public class InterfaceDesignExample : MonoBehaviour,ICanSayHello
{
///
/// 接口的隐式实现
///
public void SayHello()
{
Debug.Log("Hello");
}
///
/// 接口的显式实现
///
void ICanSayHello.SayOther()
{
Debug.Log("Other");
}
// Start is called before the first frame update
void Start()
{
// 隐式实现的方法可以直接通过对象调用
this.SayHello();
// 显式实现的接口不能通过对象调用
// this.SayOther() // 会报编译错误
// 显式实现的接口必须通过接口对象调用
(this as ICanSayHello).SayOther();
}
}
}
// 显式实现的方法必须通过接口对象调用
(this as ICanSayHello).SayOther();
如果要想调用一个对象的显式实现的方法,那么必须要将此对象强制转换成接口对象才能调用,这样就增加了调用显式实现的方法的成本,所以可以理解为这个方法被阉割了。
第一种就是比较常见的 接口-抽象类-实现类的结构,示例代码如下:
using UnityEngine;
namespace FrameworkDesign.Example
{
public class InterfaceStructExample : MonoBehaviour
{
///
/// 接口
///
public interface ICustomScript
{
void Start();
void Update();
void Destroy();
}
///
/// 抽象类
///
public abstract class CustomScript : ICustomScript
{
protected bool mStarted { get; private set; }
protected bool mDestroyed { get; private set; }
///
/// 不希望子类访问 Start 方法,因为有可能破坏状态
///
void ICustomScript.Start()
{
OnStart();
mStarted = true;
}
void ICustomScript.Update()
{
OnUpdate();
}
void ICustomScript.Destroy()
{
OnDestroy();
mDestroyed = true;
}
///
/// 希望子类实现 OnStart 方法
///
protected abstract void OnStart();
protected abstract void OnUpdate();
protected abstract void OnDestroy();
}
///
/// 由用户扩展的类
///
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");
}
}
///
/// 测试脚本
///
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();
// 可以调用 DoSomething1
onlyCanDo1.DoSomething1();
// 不能调用 DoSomething2 和 3 会报编译错误
// onlyCanDo1.DoSomething2();
// onlyCanDo1.DoSomething3();
var onlyCanDo23 = new OnlyCanDo23();
// 不可以调用 DoSomething1 会报编译错误
// onlyCanDo23.DoSomething1();
// 可以调用 DoSomething2 和 3
onlyCanDo23.DoSomething2();
onlyCanDo23.DoSomething3();
}
}
}
总结一下: