簡體   English   中英

接口和類有什么區別,為什么我可以在類中直接實現方法時使用接口?

[英]What is the difference between an interface and a class, and why I should use an interface when I can implement the methods directly in the class?

我知道這是一個非常基本的問題,但是面試官問了我很狡猾的方式,我很無助:(

我只知道接口的材料或理論定義,並且還在我參與的許多項目中實現了它。 但我真的不明白這為什么以及如何有用。

我也不明白界面中的一件事。 即例如,我們使用

conn.Dispose(); 在 finally 塊中。 但我沒有看到該類正在實現或繼承IDisposable接口( SqlConnection )類,我的意思是。 我想知道如何只調用方法名稱。 同樣在同一件事上,我不理解 Dispose 方法是如何工作的,因為我們需要使用我們自己的所有接口方法實現來實現函數體。 那么接口是如何被接受或命名為契約的呢? 直到現在,這些問題一直在我的腦海中盤旋,坦率地說,我從未見過任何能以我能理解的方式解釋我的問題的好線索。

像往常一樣 MSDN 看起來非常可怕,那里沒有任何一行是清楚的(伙計們,請原諒那些從事高級開發的人,我強烈認為任何代碼或文章都應該到達任何看到它的人的腦海中,因此就像許多其他人所說的,MSDN沒有用)。

面試官說:

他有 5 個方法,他很樂意直接在類中實現它,但是如果您必須使用抽象類或接口,您選擇哪一個,為什么? 我確實回答了他在各種博客中讀到的所有內容,說抽象類和接口的優缺點,但他不相信,他試圖理解“為什么是接口”。 “為什么是抽象類”,即使我只能實現一次相同的方法並且不會改變它。

我在網絡上看不到任何地方,我可以得到一篇文章,可以清楚地解釋接口及其功能。 我是眾多程序員中的一員,他們仍然不了解接口(我知道我使用的理論和方法)但不滿足於我清楚地理解它。

當您想創建類似的東西時,接口非常好:

using System;

namespace MyInterfaceExample
{
    public interface IMyLogInterface
    {
        //I want to have a specific method that I'll use in MyLogClass
        void WriteLog();       
    }

    public class MyClass : IMyLogInterface
    {

        public void WriteLog()
        {
            Console.Write("MyClass was Logged");
        }
    }

    public class MyOtherClass : IMyLogInterface
    {

        public void WriteLog()
        {
            Console.Write("MyOtherClass was Logged");
            Console.Write("And I Logged it different, than MyClass");
        }
    }

    public class MyLogClass
    {
        //I created a WriteLog method where I can pass as a parameter any object that implements IMyLogInterface.
        public static void WriteLog(IMyLogInterface myLogObject)
        {
            myLogObject.WriteLog(); //So I can use WriteLog here.
        }
    }

    public class MyMainClass
    {
        public void DoSomething()
        {
            MyClass aClass = new MyClass();
            MyOtherClass otherClass = new MyOtherClass();

            MyLogClass.WriteLog(aClass);//MyClass can log, and have his own implementation
            MyLogClass.WriteLog(otherClass); //As MyOtherClass also have his own implementation on how to log.
        }
    }
}

在我的示例中,我可以是編寫MyLogClass的開發人員,而其他開發人員可以創建他們的類,當他們想要記錄時,他們實現接口IMyLogInterface 就像他們問我在MyLogClass使用WriteLog()方法需要實現什么MyLogClass 他們會在界面中找到答案。

我使用接口的原因之一是因為它增加了代碼的靈活性。 假設我們得到了一個以類類型 Account 作為參數的方法,例如:

public void DoSomething(Account account) {
  // Do awesome stuff here.
}

問題在於,方法參數是針對帳戶的實現而固定的。 如果您永遠不需要任何其他類型的帳戶,這很好。 以這個例子為例,它使用一個帳戶接口作為參數。

public void DoSomething(IAccount account) {
  // Do awesome stuff here.
}

此解決方案不是針對實現而固定的,這意味着我可以向它傳遞 SuperSavingsAccount 或 ExclusiveAccount(均實現 IAccount 接口)並為每個實現的帳戶獲得不同的行為。

接口是實現者必須遵守的契約。 抽象類允許契約加上共享實現——這是接口不能擁有的。 類可以實現和繼承多個接口。 類只能擴展一個抽象類。

為什么是接口

  • 您沒有默認或共享的代碼實現
  • 您想要共享數據契約(Web 服務、SOA)
  • 每個接口實現者都有不同的實現( IDbCommandSqlCommandOracleCommand ,它們以特定方式實現接口
  • 您希望支持多重繼承

為什么要抽象

在此處輸入圖片說明

所以在這個例子中,PowerSocket 對其他對象一無所知。 這些對象都依賴於 PowerSocket 提供的 Power,因此它們實現了 IPowerPlug,這樣它們就可以連接到它。

接口很有用,因為它們提供了對象可以用來協同工作的契約,而無需了解彼此的任何其他信息。

一句話—​​—因為多態

如果您“為接口編程,而不是實現”,那么您可以將共享相同接口(類型)的不同對象作為參數注入到方法中。 這樣您的方法代碼就不會與另一個類的任何實現耦合,這意味着它始終可以使用相同接口的新創建的對象。 (開閉原則)

  • 研究依賴注入,一定要閱讀 GOF 的Design Patterns - Elements of Reusable Object-Oriented Software

我相信在問這個問題時已經流了很多血,許多人試圖通過解釋正常人無法理解的類似機器人的術語來解決這個問題。

所以首先。 要了解為什么接口和為什么抽象,您需要了解它們的用途。 我個人在申請 Factory Class 時學到了這兩個。 在這個鏈接上找到了一個很好的教程

現在讓我們根據我已經給出的鏈接進行挖掘。

您有可能會根據用戶要求更改的Vehicle類(例如添加TruckTankAirplane等。鑒於我們有

public class clsBike:IChoice
{
   #region IChoice Members
    public string Buy()
    {
       return ("You choose Bike");
    }
    #endregion
}

public class clsCar:IChoice
{
   #region IChoice Members
    public string Buy()
    {
       return ("You choose Car");
    }
    #endregion
}

並且兩者都有 Contract Ichoice ,只是說 My Class 應該有 Buy 方法

public interface IChoice
{
    string Buy();
}

現在,您會看到,該接口僅強制執行Buy()方法,但讓繼承的類決定在實現它時要執行的操作。 這是接口的局限性,使用純接口,您可能最終會重復一些我們可以使用 abstact 自動實現的任務。 在我們的例子中,假設購買每輛車都有折扣。

public abstract class Choice
{
    public abstract string Discount { get; }
    public abstract string Type { get; }
    public string Buy()
    {
       return "You buy" + Type + " with " + Discount;
}
public class clsBike: Choice
{
    public abstract string Discount { get { return "10% Discount Off"; } }
    public abstract string Type { get { return "Bike"; } }
}

public class clsCar:Choice
{
    public abstract string Discount { get { return " $15K Less"; } }
    public abstract string Type { get { return "Car"; } }
}

現在使用工廠類,您可以實現相同的目的,但是在使用抽象時,您讓基類執行Buy()方法。

總結:接口契約讓繼承類做實現,而抽象類契約可以初始化實現(可以被繼承類覆蓋)

C# 沒有鴨子類型 - 僅僅因為您知道某個方法是跨一組具體類實現的,並不意味着您可以在調用該方法時對它們一視同仁。 實現接口允許您將實現它的所有類視為相同類型的事物,這與該接口定義的內容有關。

這是一個簡單的例子:

ArrayList實現了接口IList 下面我們有一個string[]和一個List<string>並使用IList僅用一種方法操作它們:

string[] myArray = { "zero", "one", "two", "three", "four"};
List<string> myList = new List<string>{ "zero", "one", "two", "three"};

//a methode that manipulates both of our collections with IList
static void CheckForDigit(IList collection, string digit)
{
    Console.Write(collection.Contains(digit));  //checks if the collection has a specific digit
    Console.Write("----");
    Console.WriteLine(collection.ToString()); //writes the type of collection
}

static void Main()
{
    CheckForDigit(myArray, "one");   //True----System.String[]
    CheckForDigit(myList, "one");   //True----System.Collections.Generic.List`1[System.String]



//Another test:

    CheckForDigit(myArray, "four");   //True----System.String[]
    CheckForDigit(myList, "four");   //false----System.Collections.Generic.List`1[System.String]
}

使用界面,您可以執行以下操作:

1)創建分離的接口,提供不同的實現方式,允許一個更有凝聚力的接口。

2)允許接口之間有多個同名方法,因為嘿,你沒有沖突的實現,只有一個簽名。

3) 你可以獨立於你的實現來版本化和分離你的接口,確保滿足合同。

4) 你的代碼可以依賴抽象而不是具體,允許智能依賴注入,包括注入測試 Mocks 等。

我敢肯定還有更多的原因,這些只是其中的幾個。

抽象類允許你有一個部分具體的基礎來工作,這與接口不同,但有它自己的特性,比如使用模板方法模式創建部分實現的能力。

接口是對現實(對象)的抽象(類)進行抽象(原型)。

接口是在不提供類提供的實現的情況下指定合同條款。

接口是規格:

  • 接口是設計時工件,用於指定概念的固定行為,因為它是單獨的和靜態的。

  • 類是實現時間工件,用於指定現實交互和移動時的移動結構。

什么是接口?

當你觀察一只貓時,你可以說它是一種有四只爪子、一個頭、一個軀干、一條尾巴和一根毛的動物。 你可以看到他會走路、跑步、吃飯和喵喵叫。 等等。

您剛剛定義了一個帶有屬性和操作的接口。 因此,您沒有定義任何作案手法,而只是定義了特性和能力,而不知道事情是如何運作的:您已經定義了能力和區別。

因此,盡管在 UML 中我們稱其為類圖中的類,但它實際上還不是一個類,因為我們可以定義私有成員和受保護成員以開始深入了解工件。 不要在這里混淆,因為在 UML 中,接口與 C# 中的接口略有不同:它就像是抽象原子的部分訪問點。 因此我們說一個類可以實現多個接口。 因此,它是同一件事,但不是,因為 C# 中的接口既用於抽象抽象,又用於將此抽象限制為訪問點。 這是兩種不同的用途。 因此,UML 中的類表示與編程類的完全耦合接口,而 UML 接口表示編程類的一部分的解耦接口。 實際上,UML 中的類圖並不關心實現,它的所有工件都在編程接口級別。 當我們將 UML 類映射到編程類時,它是抽象抽象到具體抽象的轉換。 有一種微妙之處可以解釋設計領域和編程領域之間的二分法。 因此,從編程接口的角度來看,UML 中的類是一個編程類,同時考慮了內部隱藏的事物。

接口還允許在以笨拙的方式不可用時模擬多重繼承。 例如,cat 類將實現從動物接口派生的 cat 接口。 這個 cat 類還將實現這些接口:walk、run、eat 和 make a sound。 這彌補了類級別多重繼承的缺失,但是每次您需要重新實現所有內容時,您最多不能像現實本身那樣考慮現實。

要理解我們可以參考 Pascal 對象編碼,您在一個單元中定義接口和實現部分。 在接口中定義類型,在實現中實現類型:

unit UnitName;

interface

type
  TheClass = class
  public
    procedure TheMethod;
  end;

implementation

class procedure TheClass.TheMethod;
begin
end;

在這里,接口部分與 UML 類設計相匹配,而接口類型因此是其他東西。

因此,在我們的業務中,我們有一個詞interface來指定兩個不同但相似的事物,這是混淆的根源。

同樣在 C# 中,編程接口允許在沒有真正實現目標的情況下補償開放類型上真正的泛型多態性的缺失,因為你失去了強類型的 hability。

畢竟,接口對於允許不兼容的系統進行通信而不必擔心內存中對象的實現和管理是必要的,就像(分布式)公共對象模型引入的那樣。

什么是班級?

在從外部的角度定義了現實的簡化之后,您可以從內部的角度對其進行描述:這是您定義數據處理和消息管理以允許您封裝的現實變得生動和交互的類,謝謝到使用實例的對象。

因此,在 UML 中,您實現了機械輪子中的分形沉浸,並描述了狀態、交互等,以便能夠實現您想要處理的現實片段的抽象。

因此,從編譯器的角度來看,抽象類在某種程度上相當於接口。

更多信息

協議(面向對象編程)

C# - 接口

C# - 類

您只能從一個抽象類繼承。 您可以從多個接口繼承。 這決定了我在大多數情況下使用什么。

抽象類的優點是您可以擁有基本實現。 然而,在 IDisposable 的情況下,默認實現是無用的,因為基類不知道如何正確清理。 因此,接口會更合適。

抽象類和接口都是契約。

契約的想法是你指定一些行為。 如果你說你已經實施了,你就同意了合同。

抽象而不是接口的選擇是。

抽象類的任何非抽象后代都將實現契約。

相對

任何實現接口的類都將實現契約。

因此,當您想要指定所有后代必須實現的某些行為並保存自己定義一個單獨的接口時,您可以使用抽象,但現在滿足此有效聚合契約的所有內容都必須是后代。

讓我告訴你飛行烤面包機。

飛行烤面包機

當然,在很多情況下,您無需聲明或實現任何接口就可以構建一個可工作的軟件系統:任何面向對象的軟件設計都可以僅使用類來實現。

再說一次,任何軟件系統也可以用匯編語言實現,或者用機器代碼更好。 我們使用抽象機制的原因是因為它們傾向於使事情變得更容易。 接口就是這樣一種抽象機制。

因此,碰巧有某些非平凡的面向對象設計,如果您使用接口,它們將更容易實現,在這些情況下,接口實際上變得必要。

這些非平凡的設計與多重繼承有關,在其“真實”形式中,一個類不僅從一個基類繼承,而且從兩個或多個基類繼承。 這種真實的形式在 C# 中是不可能的,但是在 C# 和 Java 等語言出現之前,統治的語言是 C++,它完全支持真正的多重繼承。 不幸的是,真正的多重繼承被證明不是一個很好的主意,因為它極大地使語言的設計復雜化,並且還會引發各種問題,例如著名的“鑽石問題”。 (參見“多重繼承的確切問題是什么?”J Francis 的回答

因此,如果有人想要構建一個“飛行烤面包機”類,他們將繼承一些現有的“烤面包機”類以及一些現有的“飛行”類。 他們可能遇到的問題是烤面包機類的電源很可能是牆上的插座,而飛行器類的電源很可能是鴿子食,由此產生的新類要么不知何故兩者都有,或者不清楚它會有哪一個。 (鑽石問題。)

C# 和 Java 等語言的創建者決定不允許真正的多重繼承,以保持語言簡單並避免鑽石問題等陷阱。 然而,某種形式的多重繼承仍然是必要的(或者至少非常需要),因此在這些語言中,他們引入了接口作為支持較少形式的多重繼承的一種手段,同時避免了真正多重繼承的問題和復雜性。

在這種較少形式的多重繼承中,不允許一個類從多個基類繼承,但至少可以從一個或多個接口繼承。 所以,如果你想構建一個飛行烤面包機,你不能同時繼承一些現有的烤面包機類和一些現有的飛行類,但你可以做的是從現有的烤面包機類繼承,然后公開一個你自己實現的飛行接口,可能使用您已經從烤面包機繼承的任何方式。

因此,除非您覺得需要創建一個聚合兩個不同且不相關的功能集的類,否則您不需要任何形式的多重繼承,因此您不需要聲明或實現任何接口。

接口允許類設計者為最終用戶提供非常清晰的可用方法。 它們也是多態性的組成部分。

我不會針對抽象類發布接口的定義,因為我認為您非常了解理論,並且我假設您了解 SOLID 原則,所以讓我們開始實踐。

如您所知,接口不能有任何代碼,因此缺點很容易理解。

如果您需要初始化提供構造函數的類的屬性,或者您想提供實現的一部分,則抽象類將非常適合不允許您這樣做的接口。

因此,一般而言,當您需要向將繼承/擴展您的類的客戶端提供構造函數或任何代碼時,您應該更喜歡抽象類而不是接口

抽象類是為相關實體創建的,而接口可用於不相關的實體。

例如,如果我有兩個實體,比如 Animal 和 Human,那么我會選擇 Interface,好像我必須詳細說 Tiger,lion 並且想要與 Animal 聯系,然后會選擇 Animal Abstract 類。

看起來像下面

   Interface             
   ____|____
  |        |
Animal   Human



  Animal (Abstract class)
   __|___
  |      |
Tiger   Lion

暫無
暫無

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

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