C#中介面的顯式實現與隱式實現及其相關應用案例

畅知發表於2024-05-30

C#中介面的顯式實現與隱式實現

最近在學習演化一款遊戲專案框架時候,框架作者巧妙使用介面中方法的顯式實現來變相對介面中方法進行“密封”,增加實現介面的類訪問方法的“成本”。

介面的顯式實現和隱式實現:

先定義一個介面,介面中有這兩個方法。

 public interface ICanSingSong
 {
     void SingJayChow();
     void SingOther();
 }

接下來我們讓InterfaceDesignExample 繼承該介面。使用常用的隱式實現方法來實現SingJayChow方法,在Start函式中直接可以呼叫,而使用顯式實現的介面方法SingOther,則需要將類的實力轉換為介面型別才可以呼叫。

這樣相當於告訴類,ICanSingSong.SingOther()樣式就是表明SIngOther是屬於ICanSingSong介面中的“私有方法”。呼叫時候先要將類型別轉換為介面。

 public class InterfaceDesignExample : MonoBehaviour,ICanSingSong
 {
     void Start()
     {
         //介面的隱式實現 可以直接呼叫
         SingJayChow();
         
         //顯示呼叫則需要 準換成介面 呼叫
         //this.SingOther();   
         (this as ICanSingSong).SingOther();
     }

     /// <summary>
     /// 介面的隱式實現
     /// </summary>
     public void SingJayChow()
     {
         Debug.Log("你說家是唯一的城堡,隨著稻香一路奔跑~");
     }

     /// <summary>
     /// 介面的顯式實現
     /// </summary>
     void ICanSingSong.SingOther()
     { 
         Debug.Log("lalalalalalallalaal!");
     }
 }

image

我們這樣做的目的之一就是為了增加類對介面中的一些方法的呼叫成本,和使用“private”修飾有類似效果,降低對方法亂用的可能。

介面-抽象類-子類 使用顯式實現介面方法

同樣,我們先宣告一個介面:

//介面 Application
public interface IApplication
{
    void Start();
    void Update();
    void Destroy();

    void Test();
}

抽象類繼承該介面,並對生命週期函式進行顯式實現,而供子類繼承實現的方法為OnXXX

//抽象類
public abstract class Application : IApplication
{
    //不希望子類去訪問實現介面的方法
    //使用顯式呼叫
    void IApplication.Start()
    {
    	OnStart();
    }

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

    void IApplication.Destroy()
    {
    	OnDestroy();
    }

    public void Test()
    {
    Debug.Log("我是測試方法,隱式實現介面的方法,子類可以輕鬆訪問我");
    }

    //希望子類的實現的方法
    public abstract void OnStart();
    public abstract void OnUpdate();
    public abstract void OnDestroy();
}

繼承抽象類的子類對生命週期函式進行實現:

 //子類
public class SubApplication : Application
{
    public override void OnStart()
    {
        Test();
        //Start(); 情況會發生遞迴呼叫 造成堆疊溢位 而此方法使用現實實現 所以在子類中無法訪問 避免情況發生
        Debug.Log("OnStart");
    }

    public override void OnUpdate()
    {
        Debug.Log("OnUpdate");
    }

    public override void OnDestroy()
    {
        Debug.Log("OnDestroy");
    }
}

最後我們呼叫函式:

//測試呼叫
//透過介面呼叫 顯示實現的方法
IApplication application = new SubApplication();

application.Start();
application.Update();
application.Destroy();

//透過類 無法呼叫顯示實現的方法 只能訪問使用OnXXXX方法
Application application2 = new SubApplication();
application2.OnStart();
application2.OnUpdate();
application2.OnDestroy();

image

這樣我們可以體會到介面作為高抽象層的存在,可以呼叫子類具體實現的生命週期函式,而子類卻無法訪問顯示實現的介面中“私有”的生命週期函式。

介面--子介面--靜態類擴充 實現對介面函式的訪問修飾

咳咳~故事開始!

作為一個資深Jay迷,我同樣認識一個自稱曲庫的小精靈【SongLibrary】,它最拿手的三首歌曲是晴天、彩虹和說好不哭.

 //曲庫
 public class SongLibrary
 {
     public void SingSunny()
     {
    	 Debug.Log("晴天:颳風這天,我試著握你的手~");
     }
 		//彩虹
     public void SingRainbow()
     {
     	Debug.Log("彩虹:你要離開,我知道很簡單~");
     }

    public void SingNoCry()
    {
    	Debug.Log("說好不哭:說好不哭讓我走~");
    }
}

這個小曲庫精靈居住在抽象出的留聲機中,我可以透過留聲機來和它交流播放對應的歌曲。

public interface ISingAllSong
{
	SongLibrary songLibrary { get; }
}

留聲機上有三個按鈕,對應播放歌曲,

播放晴天的按鈕功能:

抽象出子介面來繼承曲庫介面,透過靜態類擴充來呼叫曲庫中對應方法播放歌曲。

public interface ISingSunny : ISingAllSong
{

}

//靜態類擴充
public static class SingSunnyExtensions
{
    public static void SingSunny(this ISingSunny self)
    {
       self.songLibrary.SingSunny();
    }
}

同樣的方式,兩個子介面。兩個繼承子介面的靜態類負責呼叫曲庫中的方法:

    //彩虹
    public interface ISingRainbow : ISingAllSong
    {
        
    }
    
    //靜態類擴充
    public static class SingRainbowExtensions
    {
        public static void SingRainbow(this ISingRainbow self)
        {
            self.songLibrary.SingRainbow();
        }
    }
    
    //說好不哭
    public interface ISingNoCry : ISingAllSong
    {
        
    }
    
    //靜態類擴充
    public static class SingNoCryExtensions
    {
        public static void SingNoCry(this ISingNoCry self)
        {
            self.songLibrary.SingNoCry();
        }
    }

這樣我們使用三個靜態類來呼叫留聲機【interface】中居住的精靈【class】的方法,實現三個按鈕功能。

當我想聽歌時候,我只需要按照我的需求,搭配繼承對應的子介面即可播放對應的歌曲,不用怕我按下去的是晴天歌曲播放按鈕而短路到播放其它的歌曲曲目。

這樣保證,拿到對應的子按鈕,只能播放對應的歌曲,保證曲目播放的有序性。

public class InterfaceRuleExample : MonoBehaviour
{
    public class OnlySingSunny : ISingSunny
    {
    	SongLibrary ISingAllSong.songLibrary { get; } = new SongLibrary();
    }

    public class OnlySingRainbowNoCry : ISingRainbow,ISingNoCry
    {
    	SongLibrary ISingAllSong.songLibrary { get; } = new SongLibrary();
    }
    void Start()
    {
        var onlySingSunny = new OnlySingSunny();
        onlySingSunny.SingSunny();
        //不能訪問
        //onlySingSunny.SingRainbow()
        //onlySingSunny.SingNoCry();

        var SingRainbowNoCry = new OnlySingRainbowNoCry();
        SingRainbowNoCry.SingRainbow();
        SingRainbowNoCry.SingNoCry();

        //無法訪問
        //SingRainbowNoCry.SingSUnny();
    }
}

image

總結一下:

使用顯示實現方式來對介面中方法進行實現,子類是無法直接呼叫的,需要將類轉換為介面型別才可以呼叫。

同時如果介面中的方法不想讓子類直接呼叫,可以讓抽象類繼承介面原生方法,在抽象類中進行方法宣告供子類呼叫,避免子類對抽象層的直接互動。同時,使用靜態類擴充來限制子介面對父介面中存在函式方法的訪問,保證類對所需方法的規範使用。

也就是說,儘可能不讓表層具象的類輕鬆的訪問到抽象層的其它不需要的功能,即類需要什麼就繼承對應的子介面,實現對應功能即可,多餘的功能不要訪問。

當然也建議閱讀一下官方社群對顯式介面的實現的解釋說明。

參考文章:

顯式介面實現 - C# | Microsoft Learn

相關文章