最近負責的 Unity 專案中,有很多 UI 有返回的按鈕,而這些按鈕也要可以透過程式觸發。如果為每個 UI 撰寫觸發的函式,一但觸發的邏輯改變,修改會是個大工程。如果為了這個小功能撰寫基礎類別,又顯得大才小用,而且 C# 的類別只能同時繼承一個類別,擴充性也不高。但 C# 類別可以繼承多個 interface,所以就思考能不能讓類別繼承 interface 的同時,也能提供功能,就好像是繼承了一個類別。

實作

作法是利用 interface 指定對象成員,再配合一個 static 類別來擴充 interface 的函式實作,來讓 interface 看起來也能提供功能。

public interface IPressBack
{
    public UnityEvent onBack { get; }
}

public static class PressBackCaller
{
    public static void PressBack(this IPressBack i)
    {
        i.onBack.Invoke();
    }
}

主要是在 static 類別上的函式參數用了 this,讓 PressBackCaller.PressBack(IPressBack) 函式能以 IPressBack.PressBack() 的形式呼叫。如此一來,繼承 interface IPressBack 的類別,就會因為 static 類別 PressBackCaller 而多了 PressBack() 的函式。這種作法稱為「擴充方法(Extension Method)」。

使用的感覺如下:

public class SomeUI : MonoBehaviour, IPressBack
{
    [SerializeField]
    private Button _backButton;

    public UnityEvent onBack => _backButton.onClick;
}

public class OtherComponent : MonoBehaviour
{
    [SerializeField]
    private SomeUI _someUI;

    private void SomeOperation()
    {
        _someUI.PressBack();
    }
}

優缺點

優點

擴充性高,能針對不同的用途提供對應的功能,而且一個類別能夠繼承多個 interface。彈性也高,如果這個 UI 不需要透過程式來觸發事件的話,就不繼承 IPressBack 就好了,就像是在「開關」這個功能。

缺點

因為是繼承自 interface,所以對象成員跟能提供的功能都必需是 public 的。所以在上面的例子裡我只開放按鈕的事件,而不是按鈕本身,除了可以保護按鈕不被修改,而且外部也只需要註冊事件的 callback 就夠了。