什么是链式编程
我想大家应该都接触过DOTween,用起来是这样的。
transform.DOMove(Vector3.one, 0.5f)
.SetEase(Ease.InBack)
.OnKill(() => Debug.Log("on killed"))
.OnComplete(() => Debug.Log("on completed"));
像以上.XXX().YYY().ZZZ()
这种写法就叫链式编程了。
C# 静态扩展快速入门
链式编程的实现技术之一是 C# 的静态扩展。静态扩展可以做到无需继承 GameObject 就可以为 GameObject 的对象添加成员方法。其实这么说不太严谨,但是看起来就是这样:)
首先我们要实现给 GameObject 添加一个DestroySelf方法。使用方式如下:
gameObject.DestroySelf();
具体实现代码 :
using System;
using UnityEngine;
public static class GameObjectExtension
{
...
public static void DestroySelf(this GameObject selfObj)
{
GameObject.Destroy(selfObj);
}
...
}
代码非常简单。
以上代码要注意的是:
1.静态扩展方法必须在静态类中实现。
2.第一个参数前要加this关键字。
当然也可以用这种方式使用:
GameObjectExtension.DestroySelf(gameObject);
这样写的意义不大,不如直接用Object/GameObject.Destroy(gameObject);不过也有可以使用的情形,就是当导出给脚本层使用的时候。这里不多说。
初步入门就介绍到这里。下面实现链式编程。
GameObject 实现链式编程
链式编程实现方式多种多样。但是对于 GameObject 来说有一种最简单并且最合适的方法,就是静态扩展 + 返回 this 的方式。
为什么呢?链式编程如果可以使用继承实现的话有很多种玩法,只不过 GameObject 是 sealed class,不能被继承。所以只能通过静态扩展 + 返回this的方式。这也是为什么会把这篇文章作为第一篇的原因。
先看下如何使用。
gameObject.Show() // active = true
.Layer(0) // layer = 0
.Name("Example"); // name = "Example"
接下来贴出实现:
using System;
using UnityEngine;
public static class GameObjectExtension
{
public static GameObject Show(this GameObject selfObj)
{
selfObj.SetActive(true);
return selfObj;
}
public static GameObject Hide(this GameObject selfObj)
{
selfObj.SetActive(false);
return selfObj;
}
public static GameObject Name(this GameObject selfObj,string name)
{
selfObj.name = name;
return selfObj;
}
public static GameObject Layer(this GameObject selfObj, int layer)
{
selfObj.layer = layer;
return selfObj;
}
public static void DestroySelf(this GameObject selfObj)
{
GameObject.Destroy(selfObj);
}
...
}
transform实现链式编程
return this + 静态扩展很容易做到GameObject链式编程,但是这样有个问题,由于静态扩展方法返回的是 MonoBehaviour 类而不是this所属的类型,所以接下来transform链式方法中只能使用 MonoBehaviour 的方法。不能像如下方式使用。
this.Position(Vector3.one)
.LocalScale(1.0f)
.Rotation(Quaternion.identity)
.DoSomething();
以上代码中,this 为 MonoBehaviour 类型的对象。
如何解决这个问题呢?答案是引入泛型。
实现代码如下所示:
public static T Position<T>(this T selfBehaviour, Vector3 position) where T : MonoBehaviour
{
selfBehaviour.transform.position = position;
return selfBehaviour;
}
public static T LocalScale<T>(this T selfBehaviour, float xyz) where T : MonoBehaviour
{
selfBehaviour.transform.localScale = Vector3.one * xyz;
return selfBehaviour;
}
public static T Rotation<T>(this T selfBehaviour, Quaternion rotation) where T : MonoBehaviour
{
selfBehaviour.transform.rotation = rotation;
return selfBehaviour;
}
实现很简单,只是把之前代码中的 MonoBehaivour 改成泛型T,然后增加一个约束: where T : MonoBehaviour。表示这个泛型一定要继承自 MonoBehaviour。这样之前例子中的this可以使用MonoBehaviour 之外的方法实现链式编程了。
不只是自己实现的 MonoBehaviour 脚本像 UGUI 的 Image 等都要支持 transform 的链式编程。那么就要找到transfom 到底是哪个类?最后找到了如下代码。
namespace UnityEngine
{
[RequiredByNativeCode]
public class Component : Object
{
public extern Transform transform { [MethodImpl(MethodImplOptions.InternalCall)] get; }
...
最终定位到,transform 是 Component 的属性器。
所以可以将之前的实现改为如下代码:
public static T Position<T>(this T selfComponent, Vector3 position) where T : Component
{
selfComponent.transform.position = position;
return selfComponent;
}
public static T LocalScale<T>(this T selfComponent, float xyz) where T : Component
{
selfComponent.transform.localScale = Vector3.one * xyz;
return selfComponent;
}
public static T Rotation<T>(this T selfComponent, Quaternion rotation) where T : Component
{
selfComponent.transform.rotation = rotation;
return selfComponent;
}
通过此种方式实现 Graphfic,Component 等类,最后可以实现如下方式的链式编程。
Image image = null;
image.LocalPosition(Vector3.back)
.ColorAlpha(0.0f)
.Hide();
当然,去查看一个属性到底是哪个类实现的这个过程也是一个很好的学习方式 : ) ,有很多类都可以实现链式编程,不过剩下的要靠大家自己了。
链式编程的优缺点
优点:代码紧凑,写起来很爽快,以自己的习惯设计接口,会提高开发效率。
缺点:性能会损耗一丢丢,调试不方便,出异常时候会发现堆栈信息超级长,别人看了会误认为Unity 升级又加了API。不过 DoTween,UniRx 都在这么用…
执行效率 vs 开发效率 + 低 bug 率,就看各位怎么权衡啦。
OK,本篇就介绍到这里。