簡體   English   中英

理解接口 V 類

[英]Understanding Interfaces V Classes

嗨,我知道這是一個老生常談的問題,但是在閱讀了以下帖子后,接口和 class 有什么區別,以及當我可以直接在 class 中實現這些方法時,為什么我應該使用接口? 我很難理解為什么真的需要使用界面。 對於這里的基本問題很抱歉,但是當我將理論作為接口和 class 之間的合同時,我似乎看不出這是多么有用。 我知道它可以幫助您輕松創建對象,但我覺得我錯過了一些東西。

我已經在這里和整個互聯網上閱讀了很多關於如何使用接口的帖子,但如果你創建一個 class 並繼承它,我有一半的時間感覺很好,它不會做同樣的事情嗎? 我在這里想念什么?

為什么是接口?

你開車嗎? 如果不是,我假設您知道駕駛汽車通常需要什么(方向盤、油門、剎車)。 答案的 rest 假設您駕駛汽車並且擁有與我的品牌不同的汽車。

你開過我的車嗎? 不會。但是如果獲得訪問權限,您是否能夠在無需學習如何駕駛我的汽車的情況下駕駛我的汽車? 是的。
這同樣適用於我。 我從來沒有開過你的車,但我可以駕駛它而無需學習如何駕駛它。

這是為什么? 因為所有汽車共享相同的界面 方向盤,油門在右邊,剎車在中間。 沒有兩輛車是完全相同的,但它們的制造方式是駕駛員與任何汽車之間的交互是完全相同的。

將此與 F16 戰斗機進行比較。 能夠駕駛汽車並不能使您能夠駕駛噴氣式飛機,因為它的界面不同 它沒有方向盤,也沒有油門/剎車踏板。

主要好處很明顯:駕駛員無需學習如何單獨駕駛每輛車。

現在,為了完成類比,汽車的一般概念是接口,而特定汽車是類。 主要好處很明顯:您不需要為每個類似的 class 編寫自定義代碼。


一個實際的例子

public class BMW 
{
    public SteeringWheel SteeringWheel { get; set; }
    public Pedal Accelerator { get; set; }
    public Pedal Brake { get; set; }
}

public class BMWDriver
{
    public void ParticipateInRace(BMW myBMW) 
    {
        myBMW.Accelerator.Press();

        myBMW.SteeringWheel.TurnLeft();
        myBMW.SteeringWheel.TurnRight();

        myBMW.Accelerator.Release();
        myBMW.Brake.Press();

        myBMW.Brake.Release();
    }
}

這個司機只知道怎么開寶馬。

public class Audi 
{
    public SteeringWheel SteeringWheel { get; set; }
    public Pedal Accelerator { get; set; }
    public Pedal Brake { get; set; }
}

public class AudiDriver
{
    public void ParticipateInRace(Audi myAudi) 
    {
        myAudi.Accelerator.Press();

        myAudi.SteeringWheel.TurnLeft();
        myAudi.SteeringWheel.TurnRight();

        myAudi.Accelerator.Release();
        myAudi.Brake.Press();

        myAudi.Brake.Release();
    }
}

這個司機只知道怎么開奧迪。

但實際上,司機可以駕駛任何汽車(有一個方向盤和兩個踏板)。

那么我們如何告訴編譯器可以使用任何汽車呢? 我們給了它們一個共同點:界面。

public interface ICar
{
    SteeringWheel SteeringWheel { get; }
    Pedal Accelerator { get; }
    Pedal Brake { get; }
}

public class BMW : ICar { /* same as before */ }

public class Audi : ICar { /* same as before */ }

public class Driver
{
    public void ParticipateInRace(ICar anyCar)
    {
        anyCar.Accelerator.Press();

        anyCar.SteeringWheel.TurnLeft();
        anyCar.SteeringWheel.TurnRight();

        anyCar.Accelerator.Release();
        anyCar.Brake.Press();

        anyCar.Brake.Release();
    }
}

我們現在有一個更通用的Driver ,他能夠駕駛任何有方向盤和兩個踏板的汽車。


為什么不是 inheritance?

如果你創建一個 class 並繼承它,我有一半的時間會不會做同樣的事情? 我在這里想念什么?

在某些情況下,inheritance 會起作用。 然而,inheritance 通常是一個較差的解決方案,尤其是當您進入更復雜的代碼庫或更高級的架構時。

不用擔心,所有開發人員都曾經喜歡 inheritance,然后需要學會不使用 inheritance 作為萬靈葯。 這是開發人員正常生命周期的一部分:)

最大的原因之一是您不能從多個 class 派生,但您可以實現多個接口

假設我們有三種可以做的運動

public class Runner 
{  
    public void Run() { /* running logic */ } 
}

public class Swimmer
{  
    public void Swim() { /* swimming logic */ } 
}

public class Cyclist
{  
    public void Cycle() { /* cycling logic */ } 
}

現在我們需要創建一項需要跑步的專業運動,例如籃球。

public class BasketBallPlayer : Runner 
{ 
    public void ThrowBall() { /* throwing logic */ }

    // Running is already inherited from Runner
}

很好,還沒有問題。 但是現在,我們需要創建一個鐵人三項運動員 class,它需要所有三項運動(跑步、游泳、騎自行車)

public class Triathlete: Runner, Swimmer, Cyclist { ... }

這就是編譯器崩潰的地方。 它拒絕允許您同時從多個基類繼承。 原因比這個答案可以深入得多,如果你想了解更多,請谷歌它。

但是,如果我們使用接口:

public interface IRunner
{
    void Run();
}

public interface ISwimmer
{
    void Swim();
}

public interface ICyclist
{
    void Cycle();
}

然后編譯器會允許這樣做:

public class Triathlete: IRunner, ISwimmer, ICyclist 
{ 
    public void Run() { /* running logic */ } 

    public void Swim() { /* swimming logic */ } 

    public void Cycle() { /* cycling logic */ } 
}

這就是為什么接口通常擊敗 inheritance (以及其他原因)。

接口通常更好的原因還有很多,但這是最大的一個。 如果您需要更多解釋,請在 Google 上搜索,我無法深入研究 StackOverflow 的答案。

您(整個算法的創建者)根本不知道實現的一種情況。

將它們轉換為現實生活場景:FxCop + StyleCop 都使用訪問者模式來掃描代碼。 因此,此示例中的工具 (FxCop) 的創建者有一些基本代碼,這些代碼與一些 UI/CLI 耦合,並期望掃描結果中的某些特定屬性,例如嚴重性/問題等。

雖然 FxCop 附帶默認規則,但作為最終客戶,您也可以根據自己的喜好擴展這些規則。 FxCop 做到這一點的唯一方法是依賴多態接口/抽象類。

因此,FxCop 工具需要一個規則實例來檢測某些東西並報告成功或失敗。

但是您的組織可能有一個只有您需要的自定義規則。 假設它是這樣的:我們所有的命名空間都必須以 myorg.mytool 開頭

這是您必須使用抽象的示例(不能只在 class 中預先實現代碼),因為 Microsoft 對您在組織中實施的自定義代碼規則一無所知。

另一個例子是領域驅動設計中領域和基礎設施代碼的分離方式。

因此,假設您有一個圖書收藏應用程序。 一個你可以得到一本書的地方,一個作者的所有書籍書籍等。

然后,您將有一個域類型調用,例如 BookRepository,您的所有書籍都將在其中保存。 這有兩個方面:1. 放置書籍的所有處理邏輯的域和 2. 持久性代碼(IO/數據庫或其他)。

將這兩者分開的原因是因為域邏輯(業務邏輯)不會與持久性代碼糾纏在一起。 域代碼不想知道一本書是如何被持久化的。 它只關心您可以對一本書做什么(按作者獲取、購買、出售等)。

在這種情況下,當您在域代碼中放置一個名為 IBookRepository 之類的接口時,該接口就會出現,您 go 會通過單元測試創建您需要的所有代碼。 在這一點上,您並不關心書籍是如何存儲的——您只關心它們是如何存儲的。 然后另一個團隊或以后,您可以深入了解有關該書如何恢復的詳細信息。 在數據庫中,在緩存中或其他東西中。 持久性代碼也可以在不觸及作為持續發布原則的組成部分的域代碼的情況下發展:盡可能小的更新。 換句話說,它允許您在觸及業務代碼的情況下發布基礎架構更新。 可能是您的應用程序運行良好,但您不想更新數據庫驅動程序。

抽象類是介於接口和類之間的東西,但應該與接口類似地使用/它們在使用上更接近於接口而不是類。

** 最后,使用接口的另一個原因是接口可以被認為是一個方面,您可以將多個接口(方面)應用於單個 class(多重繼承)而幾乎沒有摩擦,而將其放在類中會迫使您做單個inheritance 可能導致大型且過於復雜的 inheritance 層次結構。

希望對你有所幫助

本質上,我對每種情況都用盡可能少的詞:

  • 當您有不同的實體共享共同的行為時,應該使用抽象。
  • 當您希望不同的實體work使用相同的代碼時,應該使用接口

例如,您將在以下情況下使用抽象:

您想創建一個處理動物的應用程序。 您創建一個抽象的 class Animal ,其中包含一些字段NoLegsHasTailNoEyes 您創建了DogCatPanda class ,它們都繼承自Animal 現在您最終擁有 3 個類,但共享代碼在另外 1 個 class 中定義,因為它們都具有這些描述性特征。

現在對於接口:在項目的服務中,您創建一個名為“StartRunning”的方法。 該方法的定義是:

public void StartRunning(List<ICanRun> thingsThatRun)
{
    thingsThatRun.forEach(t => t.StartRunning());
}

現在你 go 回到你的動物抽象 class 並聲明它應該實現ICanRun接口,編譯器將強制你Dog並在CatPanda類上定義方法, 現在最重要的是:

  1. 您只定義了一次在不同對象中通用的屬性。 (眼睛的數量是描述動物時常見的特征)
  2. 每只動物的奔跑方式都不同。您的狗奔跑方法可以“直接向前奔跑”,而您的貓奔跑方法可以“尋找最近的牆壁並爬上去”。 熊貓可能會throw導致 pandas 不運行。

接口的真正魔力:因為您的 StartRunning 方法不考慮類,而是將對象傳遞給ICanRun接口,所以您可以擁有另一個 class,例如Bike ,它也實現ICanRun並定義方法StartRunning 然后,您可以創建一個包含狗、貓和自行車的列表,並將這 3 個不相關的類放到同一個列表中,並將該列表傳遞給 StartRunning,它們都會“開始跑步”。

List<ICanRun> runableThings = new List<ICanRun>(){new Dog(), new Cat(), new Bike()};
StartRunning(runableThings);

我希望這個例子有幫助

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM