C#快速入門教程(16)—— 介面

曹化宇發表於2018-10-11

介面(interface)的概念,在現實生活中是很常見的,標準化介面帶來的好處就是,不同廠商、不同型別的裝置之間可以有效地配合工作,如電腦中的USB介面就是一個非常典型的代表。

軟體開發中,使用介面的意義也是這樣,通過介面對元件進行組合,可以提高軟體系統各元件的獨立性,使得開發、維護和擴充套件都會更加靈活和高效。在C#中,介面型別是比抽象類更加抽象的型別,因為介面中只允許定義成員,並沒有任何實現程式碼。

下面的程式碼,我們定義了兩個介面型別,分別是IDbEngine和IDbRecord介面。

namespace ConsoleTest
{
    //
    public interface IDbEngine
    {
        string CnnStr { get; }
        string Type { get; }
        bool Connected { get; }
        object GetValue(string sql);
    }
    //
    public interface IDbRecord
    {
        IDbEngine DbEngine { get; }
        string TableName { get;}
        void Insert();
        void Delete();
        void Update();
    }
}

這是模擬的資料庫操作元件的兩個介面型別,這裡進行了一些簡化,完整的元件設計思路可以參考作者的個人網站相關內容。

IDbEngine元件用於資料庫的基本操作,成員包括:

  • CnnStr只讀屬性,表示資料庫連線字串。
  • Type只讀屬性,表示資料庫型別,如SQL Server、MySQL等。
  • Connected只讀屬性,表示資料庫是否能夠正確連線。
  • GetValue()方法,用於執行SQL語句,並返回一個值。

IDbRecord元件用於資料表的記錄操作,成員包括:

  • DbEngine屬性,表示資料庫引擎型別,即上述定義的IDbEngine型別。
  • TableName屬性,表示所操作的資料表名稱。
  • Insert()方法,用於在資料表中插入新記錄。
  • Delete()方法,用於在資料表刪除資料。
  • Update()方法,用於更新資料表記錄。

資料庫操作在很多軟體系統中是非常重要的組成部分,而簡化資料庫操作程式碼也是開發者需要考慮的問題,IDbEngine和IDbRecord介面元件就是為了能夠使用統一的程式碼操作多種資料庫,從而簡化資料操作程式碼,提高軟體開發效率。

開發中,我們並不能建立介面型別的物件,而是需要真正的類來建立物件,下面的程式碼,我們建立了CSqlEngine和CMySqlEngine類,分別完成SQL Server資料庫和MySQL資料庫的基本操作。

//
public class CSqlEgnine : IDbEngine
{
    public string CnnStr
    {
        get { return "SQL Server Connection String"; }
    }
    public string Type
    {
        get { return "SQL Server"; }
    }
    public bool Connected
    {
        get { return true; }
    }
    public object GetValue(string sql)
    {
        return "來自SQL Server資料庫的值";
    }
}
//
public class CMySqlEgnine : IDbEngine
{
    public string CnnStr
    {
        get { return "MySQL Connection String"; }
    }
    public string Type
    {
        get { return "MySQL"; }
    }
    public bool Connected
    {
        get { return true; }
    }
    public object GetValue(string sql)
    {
        return "來自MySQL資料庫的值";
    }
}

下面的程式碼,我們建立CDbRecord類,其實現了IDbRecord介面。

public class CDbRecord : IDbRecord
{
    // 建構函式
    public CDbRecord(IDbEngine dbe,string sTableName)
    {
        DbEngine = dbe;
        TableName = sTableName;
    }
    //
    public IDbEngine DbEngine { get; private set; }
    //
    public string TableName { get; private set; }
    //
    public void Insert()
    {
        Console.WriteLine("插入{0}資料庫{1}表的記錄",
            DbEngine.Type, TableName);
    }
    //
    public void Delete()
    {
        Console.WriteLine("刪除{0}資料庫{1}表的記錄",
            DbEngine.Type, TableName);
    }
    //
    public void Update()
    {
        Console.WriteLine("更新{0}資料庫{1}表的記錄",
            DbEngine.Type, TableName);
    }
}

下面的程式碼演示瞭如何通過介面型別靈活完成不同型別資料庫中資料記錄的操作。

static void Main(string[] args)
{
        // 操作SQL Server
        IDbEngine dbe = new CSqlEgnine();
        IDbRecord rec = new CDbRecord(dbe, "UserMain");
        rec.Insert();
        rec.Delete();
        rec.Update();
        // 操作MySQL資料庫
        dbe = new CMySqlEgnine();
        rec = new CDbRecord(dbe, "UserMain");
        rec.Insert();
        rec.Delete();
        rec.Update();
}

程式碼中首先建立了SQL Server資料庫操作引擎dbe物件,它定義為IDbEngine介面;接下來,建立了操作SQL Server資料庫的rec物件,其宣告為IDbRecord型別,例項化為CDbRecord類;然後,將dbe例項化為操作MySQL資料庫的CMySqlEngine型別物件,使用相同的程式碼建立重新建立了CDbRecord物件,並IDbRecord介面中相同的Insert()、Delete()和Update()方法完成了不同的資料庫中的記錄操作。執行結果如下圖所示。

enter image description here

在這個示例中,只需要修改IDbEngine物件的定義就可以使用相同的程式碼來運算元據記錄(IDbRecord元件);一方面,如果專案中需要使用不同的資料庫,可以很方便的切換;另一方面,可以大量地簡化資料操作程式碼,從而將注意力更多地放在主要的業務和軟體功能上。

當然,如果要實現完整的資料庫操作,還需要大量的程式碼,在後續的課程中會討論SQL Server資料庫的基礎應用,而更多關於資料庫和資料操作元件的內容可以參考作者個人網站中的相關資訊。

接下來,我們討論一些在C#中使用介面時需要注意的問題。

首先來看一下介面的繼承問題。類在繼承時只能指定一個父類,而介面則不同,我們可以讓一個介面繼承多個介面,也可以讓一個類實現多個介面。先來看下面的程式碼。

//
public interface I1
{
    void DoWork1();
}
//
public interface I2
{
    void DoWork2();
}
//
public interface I3 : I1, I2
{

}
//
public class C3 : I3
{
    public void DoWork1()
    {
        Console.WriteLine("I1.DoWork1");
    }
    //
    public void DoWork2()
    {
        Console.WriteLine("I2.DoWork2");
    }
}

程式碼,I3介面繼承了I1和I2介面,這樣,在C3類實現I3介面時,就必須實現DoWork1()和DoWork2()兩個方法;下面的程式碼演示了這幾個介面和C3類的應用。

static void Main(string[] args)
    {
        I3 c3 = new C3();
        c3.DoWork1();
        c3.DoWork2();
        //
        (c3 as I1).DoWork1();
        (c3 as I2).DoWork2();
    }

程式碼執行結果如下圖所示。

enter image description here

現在的問題是,如果類實現的多個介面中有同名的成員時應該如何處理,如下面的程式碼。

//
public interface I1a
{
    void DoWork();
}
//
public interface I1b
{
    void DoWork();
}
//
public class C1 : I1a,I1b
{
    public void DoWork()
    {
        Console.WriteLine("I1a.DoWork");
    }
}

如果兩個介面中同名成員的功能是相同的,就可以只在類中實現一個方法,如上述程式碼一樣,下面的程式碼演示了C1類的使用。

static void Main(string[] args)
{
        C1 c1 = new C1();
        c1.DoWork();
}

程式碼執行結果如下圖所示。

enter image description here

那麼,如果I1a和I1b介面中的DoWork()方法功能不同該怎麼辦呢?答案是使用一個不同的成員來實現,如下面的程式碼,我們在C1類中新增一個方法,並指定其實現的是哪個介面中的哪個方法。

public class C1 : I1a, I1b
{
    public void DoWork()
    {
        Console.WriteLine("I1a.DoWork");
    }
    //
    void I1b.DoWork()
    {
        Console.WriteLine("I1b.DoWork");
    }
}

這裡,預設實現的是I1a介面中的DoWork()方法,而I1b介面中的DoWork()方法就必須顯示呼叫。下面的程式碼演示了這兩個方法的使用。

static void Main(string[] args)
{
        C1 c1 = new C1();
        c1.DoWork();
        (c1 as I1b).DoWork();
}

程式碼執行結果如下圖所示。

enter image description here

實際應用中,還可以將所有同名成員都使用介面進行限定,只是需要注意在定義這些成員時不需要使用public等修飾符,因為作為介面成員,它們預設都應用是public訪問級別。

介面的應用,為程式碼組合帶來極大的靈活性,大家可以在學習和工作中逐步掌握其強大功能,並能夠靈活、高效地應用。

CHY軟體小屋原創作品!

相關文章