接口的“阉割”

利用C#接口的显式实现,来达到接口方法在子类“阉割”的目的。

在FrameworkDesign——Example文件夹下新建文件夹InterfaceDesign,在其中新建InterfaceDesignExample场景以及InterfaceDesignExample脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using UnityEngine;

namespace FrameWorkDesign.Example
{
public interface ICanSayHello
{
void SayHello();
void SayOther();
}
public class InterfaceDesignExample : MonoBehaviour,ICanSayHello
{
public void SayHello()
{
Debug.Log("Hello!");
}

void ICanSayHello.SayOther()//显式实现接口,不能使用访问修饰符,因为必须通过接口访问
{
Debug.Log("Other");
}

private void Start()
{
this.SayHello();
(this as ICanSayHello).SayOther();//显式实现接口的方法不能直接调用,需要将实现接口的类显式转换成接口
}
}
}

上面利用接口的显式实现,我们在调用相关方法时必须显式转换接口的思想我们命名为接口的”阉割”,这个名称是原创的,并不是术语。

利用接口的显式实现,我们在调用相关方法时就会有限制,这种限制就是“阉割”这个含义的本意。

一般在两种情况下使用,一种是接口-抽象类-实现类,一种是接口-静态拓展来实现方法访问规则

接口——抽象类——实现类

在文件夹InterfaceDesign中新建InterfaceStructExample脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
using UnityEngine;

public class InterfaceStructExample : MonoBehaviour
{
public interface ICustomScript
{
void Start();
void Update();
void Destroy();
}

public abstract class CustomScript : ICustomScript
{
void ICustomScript.Destroy()
{
OnDestroy();
}

void ICustomScript.Start()
{
OnStart();
}

void ICustomScript.Update()
{
OnUpdate();
}

protected abstract void OnStart();
protected abstract void OnUpdate();
protected abstract void OnDestroy();
}
public class MyScript : CustomScript
{
protected override void OnDestroy()
{
//Destroy();无法访问,这样就可以防止递归死循环
}

protected override void OnStart()
{
//Start();无法访问,这样就可以防止递归死循环
}

protected override void OnUpdate()
{
//Update();无法访问,这样就可以防止递归死循环
}
}
void Start()
{
//一般是底层代码
ICustomScript myScript = new MyScript();//只能通过声明接口来调用Start等方法
myScript.Start();
myScript.Update();
myScript.Destroy();
}
}

这样就可以限制子类调用不应该调用的方法,子类实例化时再用接口声明,这样就又能调用不应该调用的方法了

接口-静态拓展来实现方法访问规则

在文件夹InterfaceDesign中新建InterfaceRuleExample脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
using UnityEngine;

namespace FrameWorkDesign.Example
{
#region 底层设计代码
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; }
}
#region 接口和它们各自的拓展方法
public interface ICanDoSomething1 : IHasEverything
{

}

public static class ICanDoSomething1Extension
{
public static void DoSomething1(this ICanDoSomething1 self)
{
self.CanDoEverything.DoSomething1();
}
}

public interface ICanDoSomething2 : IHasEverything
{

}

public static class ICanDoSomething2Extension
{
public static void DoSomething2(this ICanDoSomething2 self)
{
self.CanDoEverything.DoSomething2();
}
}

public interface ICanDoSomething3 : IHasEverything
{

}

public static class ICanDoSomething3Extension
{
public static void DoSomething3(this ICanDoSomething3 self)
{
self.CanDoEverything.DoSomething3();
}
}
#endregion
#endregion
public class InterfaceRuleExample : MonoBehaviour
{
public class OnlyCanDo1 : ICanDoSomething1
{
CanDoEverything IHasEverything.CanDoEverything { get; } = new CanDoEverything();
}
public class CanDo2and3 : ICanDoSomething2, ICanDoSomething3
{
CanDoEverything IHasEverything.CanDoEverything { get; } = new CanDoEverything();
}

private void Start()
{
var onlyCanDo1 = new OnlyCanDo1();
onlyCanDo1.DoSomething1();
var canDo2and3 = new CanDo2and3();
canDo2and3.DoSomething2();
canDo2and3.DoSomething3();
//如果想获取所有的方法,就用接口来声明
//IHasEverything canDoEverything = new OnlyCanDo1();
//canDoEverything.CanDoEverything.DoSomething1();
//canDoEverything.CanDoEverything.DoSomething2();
//canDoEverything.CanDoEverything.DoSomething3();
}
}
}

上面代码中我们可以看到

  • 通过接口写拓展方法,实现了接口的类可以直接调用,注意这个拓展方法是内部有实现的,不是接口内部声明的方法规则那样的
  • 这里使用接口“阉割”并不是上面那样父类限制子类访问,而是限制一个类限制自己的实例访问

总结

  • 接口阉割技术本质是利用接口的显式实现限制访问
  • 接口-抽象类-实现类时,不想被乱调用某些方法可以用接口阉割技术
  • 接口-静态拓展时,通过实现接口就能调用接口的拓展方法,我们只想要接口的拓展方法而防止访问到其他对象时可以用接口阉割技术

接口的显式实现本来的用途是当很多接口的规定的方法名称都相同时(也可以描述为需要实现多个签名一致的方法时),通过接口的显式声明来区分到底哪个方法属于哪个接口