2010-04-23 97 views
87

在閱讀有關設計模式時,有人會絆住這句話。「程序接口,不是實現」是什麼意思?

但我不明白,有人可以解釋這對我嗎?

+2

可能的重複[什麼意思是「編程接口」?](http://stackoverflow.com/questions/383947/what-does-it-mean-to-program-to-一個接口) – 2015-11-26 06:35:20

回答

106

接口只是合同或簽名,他們不知道什麼 約實現。

對接口進行編碼意味着客戶端代碼始終保存由工廠提供的接口對象。工廠返回的任何實例都將是類型爲Interface的工廠候選類必須實現的類型。通過這種方式,客戶端程序並不擔心實現,接口簽名確定所有操作都可以完成。這可以用來在運行時更改程序的行爲。它還可以幫助您從維護的角度編寫出更好的程序。

下面是您的一個基本示例。

public enum Language 
{ 
    English, German, Spanish 
} 

public class SpeakerFactory 
{ 
    public static ISpeaker CreateSpeaker(Language language) 
    { 
     switch (language) 
     { 
      case Language.English: 
       return new EnglishSpeaker(); 
      case Language.German: 
       return new GermanSpeaker(); 
      case Language.Spanish: 
       return new SpanishSpeaker(); 
      default: 
       throw new ApplicationException("No speaker can speak such language"); 
     } 
    } 
} 

[STAThread] 
static void Main() 
{ 
    //This is your client code. 
    ISpeaker speaker = SpeakerFactory.CreateSpeaker(Language.English); 
    speaker.Speak(); 
    Console.ReadLine(); 
} 

public interface ISpeaker 
{ 
    void Speak(); 
} 

public class EnglishSpeaker : ISpeaker 
{ 
    public EnglishSpeaker() { } 

    #region ISpeaker Members 

    public void Speak() 
    { 
     Console.WriteLine("I speak English."); 
    } 

    #endregion 
} 

public class GermanSpeaker : ISpeaker 
{ 
    public GermanSpeaker() { } 

    #region ISpeaker Members 

    public void Speak() 
    { 
     Console.WriteLine("I speak German."); 
    } 

    #endregion 
} 

public class SpanishSpeaker : ISpeaker 
{ 
    public SpanishSpeaker() { } 

    #region ISpeaker Members 

    public void Speak() 
    { 
     Console.WriteLine("I speak Spanish."); 
    } 

    #endregion 
} 

alt text http://ruchitsurati.net/myfiles/interface.png

這僅僅是一個基本的例子和原則 實際的解釋是 超出了這個答案的範圍。

EDIT

我已經更新上面的例子中,並添加一個抽象的揚聲器基類。在此次更新中,我向所有Spakers添加了一個「SayHello」功能。所有發言者都會說「Hello World」。所以這是一個具有類似功能的常見功能。參考類圖,您會發現Speaker抽象類實現ISpeaker接口並將Speak()標記爲抽象,這意味着每個Speaker實現都負責實施Speak方法,因爲它從Speaker到Speaker不同。但是所有發言者一致地說「你好」。因此,在抽象的Speaker類中,我們定義了一個說「Hello World」的方法,每個Speaker實現將派生出SayHello方法。

考慮一個西班牙語演講者不能說你好的情況,所以在這種情況下,你可以重寫西班牙語演講者的SayHello方法並引發適當的異常。

請注意,我們已經 沒有做出任何更改界面 ISpeaker。而客戶端代碼和SpeakerFactory也不會影響 不變。這是我們通過編程接口實現的。

,我們可以實現通過簡單地增加一個抽象基類揚聲器和一些細微的修改中的每一個執行從而離開原來的程序不變,此行爲。這是任何應用程序的理想功能,它使您的應用程序易於維護。

public enum Language 
{ 
    English, German, Spanish 
} 

public class SpeakerFactory 
{ 
    public static ISpeaker CreateSpeaker(Language language) 
    { 
     switch (language) 
     { 
      case Language.English: 
       return new EnglishSpeaker(); 
      case Language.German: 
       return new GermanSpeaker(); 
      case Language.Spanish: 
       return new SpanishSpeaker(); 
      default: 
       throw new ApplicationException("No speaker can speak such language"); 
     } 
    } 
} 

class Program 
{ 
    [STAThread] 
    static void Main() 
    { 
     //This is your client code. 
     ISpeaker speaker = SpeakerFactory.CreateSpeaker(Language.English); 
     speaker.Speak(); 
     Console.ReadLine(); 
    } 
} 

public interface ISpeaker 
{ 
    void Speak(); 
} 

public abstract class Speaker : ISpeaker 
{ 

    #region ISpeaker Members 

    public abstract void Speak(); 

    public virtual void SayHello() 
    { 
     Console.WriteLine("Hello world."); 
    } 

    #endregion 
} 

public class EnglishSpeaker : Speaker 
{ 
    public EnglishSpeaker() { } 

    #region ISpeaker Members 

    public override void Speak() 
    { 
     this.SayHello(); 
     Console.WriteLine("I speak English."); 
    } 

    #endregion 
} 

public class GermanSpeaker : Speaker 
{ 
    public GermanSpeaker() { } 

    #region ISpeaker Members 

    public override void Speak() 
    { 
     Console.WriteLine("I speak German."); 
     this.SayHello(); 
    } 

    #endregion 
} 

public class SpanishSpeaker : Speaker 
{ 
    public SpanishSpeaker() { } 

    #region ISpeaker Members 

    public override void Speak() 
    { 
     Console.WriteLine("I speak Spanish."); 
    } 

    public override void SayHello() 
    { 
     throw new ApplicationException("I cannot say Hello World."); 
    } 

    #endregion 
} 

alt text http://demo.ruchitsurati.net/myfiles/interface1.png

+15

對接口的編程不僅僅是關於參考變量的類型。這也意味着你不會對你的實現使用任何隱含的假設。例如,如果你使用'List'作爲類型,你仍然可以通過反覆調用'get(i)'來假定隨機訪問是快速的。 – 2010-04-23 11:36:00

+11

工廠與接口編程是正交的,但我認爲這個解釋使得它看起來好像是它的一部分。 – 2010-04-23 13:58:50

+0

@ Toon:同意你的看法。我想爲編程接口提供一個非常基本和簡單的例子。我不想通過在少數鳥類和動物類上實現IFlyable接口來混淆提問者。 – 2010-04-23 14:09:56

13

這意味着您應該嘗試編寫代碼,以便它使用抽象(抽象類或接口)而不是直接實現。

通常通過構造函數或方法調用將實現注入到代碼中。因此,您的代碼知道接口或抽象類,並可以調用此合同中定義的任何內容。作爲一個實際的對象(接口/抽象類的實現)被使用,調用在對象上運行。

這是Liskov Substitution Principle(LSP)的一個子集,即SOLID原理的L.

在.NET的一個例子是與IList而不是ListDictionary的代碼,所以你可以使用在你的代碼互換實現IList任何類:

// myList can be _any_ object that implements IList 
public int GetListCount(IList myList) 
{ 
    // Do anything that IList supports 
    return myList.Count(); 
} 

從基類庫另一個例子( BCL)是抽象類 - ProviderBase - 這提供了一些基礎結構,同樣重要的是,如果您對它進行編碼,所有提供者實現都可以互換使用。

+0

但客戶端如何與接口交互並使用其空方法? – 2010-04-23 10:49:34

+0

客戶端不會與接口進行交互,而是通過接口進行交互:)對象通過方法(消息)與其他對象進行交互,並且接口是一種語言 - 當您知道某個對象(人)實現(講話)時, IList),你可以在不需要知道更多關於該對象(他也是意大利人)的情況下使用它,因爲在這種情況下它不是必需的(如果你想要求幫助,你不需要知道他也會說意大利語,如果你懂英語)。 – 2010-04-23 10:59:26

+0

BTW。恕我直言,Liskov替代原則是關於繼承的語義,並且與接口無關,可以在沒有繼承的語言中找到(來自Google)。 – 2010-04-23 11:01:01

4

這種說法是對的耦合。使用面向對象編程的一個潛在原因是重用。因此,例如,您可以將算法分解爲兩個協作對象A和B.這對於稍後創建另一個算法可能很有用,該算法可能會重用這兩個對象中的一個或另一個。但是,當這些對象進行通信(發送消息 - 調用方法)時,它們會相互創建依賴關係。但是如果你想使用一個沒有另一個,你需要指定如果我們替換B,應該爲對象A做些什麼其他對象C do。這些描述稱爲接口。這允許對象A在不改變的情況下與依賴於該接口的不同對象進行通信。你提到的陳述說,如果你打算重新使用算法的一部分(或者更通常的是一個程序),你應該創建接口並依賴它們,所以你可以在任何時候改變具體的實現而不用改變其他對象,如果你使用聲明的接口。

26

將接口想象爲對象與客戶端之間的契約。接口指定了對象可以執行的操作,以及用於訪問這些事件的簽名。

實現是實際的行爲。比如說你有一個sort()方法。您可以實現QuickSort或MergeSort。只要接口不更改,對客戶端代碼調用排序應該沒有關係。

像Java API和.NET Framework這樣的庫大量使用接口,因爲數百萬程序員使用提供的對象。這些庫的創建者必須非常小心,他們不會更改這些庫中的類的接口,因爲它會影響使用該庫的所有程序員。另一方面,他們可以根據自己的喜好改變實施方式。

如果作爲一名程序員,您對代碼執行代碼,那麼只要它改變您的代碼停止工作。所以想想這個接口的好處:

  1. 它隱藏了你不需要知道的東西使對象更簡單的使用。
  2. 它提供的對象將如何表現,所以你可以依靠的是
+0

它的確意味着你需要知道你正在簽訂的對象是什麼:在這個例子中,你只是訂約,而不一定是穩定的訂單。 – penguat 2010-04-23 13:23:51

1

接口描述能力的合同。在編寫命令性代碼時,請講述您正在使用的功能,而不是特定的類型或類。

2

正如其他人所說的那樣,這意味着您的調用代碼應該只知道抽象的父代,而不是實際執行的類。

有什麼有助於理解這是你應該總是編程到接口的原因。原因很多,但最容易解釋的有兩個:

1)測試。

比方說,我有我的整個數據庫代碼在一個類。如果我的程序知道具體的類,我只能通過對該類的真正運行來測試我的代碼。我正在使用 - >來表示「與...對話」。

WorkerClass - > DALClass 但是,讓我們添加一個接口到混音中。

WorkerClass - > IDAL - > DALClass。

所以DALClass實現了IDAL接口,而工人類只通過這個來調用。

現在,如果我們想爲代碼編寫測試,我們可以改爲創建一個類似數據庫的簡單類。

WorkerClass - > IDAL - > IFakeDAL。

2)重用

按照上面的例子,假設我們想從SQL Server(其中我們的具體DALClass使用)來MonogoDB移動。這將需要大量工作,但如果我們已經編程到一個界面,則不會。在這種情況下,我們只寫了新的DB類,並改變(通過工廠)

WorkerClass - > IDAL - > DALClass

WorkerClass - > IDAL - > MongoDBClass

4

如果你將在Combustion-Car時代寫一個汽車類,那麼你很有可能會實施oilChange()作爲這個類的一部分。但是,當引入電動汽車時,由於這些汽車沒有涉及換油,並且沒有實施,您將會遇到麻煩。

問題的解決方案是在Car類中有一個performMaintenance()接口,並在適當的實現中隱藏細節。每個Car類型將爲performMaintenance()提供它自己的實現。作爲汽車的所有者,您必須處理的是performMaintenance(),而不必擔心在發生更改時進行調整。

class MaintenanceSpecialist { 
    public: 
     virtual int performMaintenance() = 0; 
}; 

class CombustionEnginedMaintenance : public MaintenanceSpecialist { 
    int performMaintenance() { 
     printf("combustionEnginedMaintenance: We specialize in maintenance of Combustion engines \n"); 
     return 0; 
    } 
}; 

class ElectricMaintenance : public MaintenanceSpecialist { 
    int performMaintenance() { 
     printf("electricMaintenance: We specialize in maintenance of Electric Cars \n"); 
     return 0; 
    } 
}; 

class Car { 
    public: 
     MaintenanceSpecialist *mSpecialist; 
     virtual int maintenance() { 
      printf("Just wash the car \n"); 
      return 0; 
     }; 
}; 

class GasolineCar : public Car { 
    public: 
     GasolineCar() { 
     mSpecialist = new CombustionEnginedMaintenance(); 
     } 
     int maintenance() { 
     mSpecialist->performMaintenance(); 
     return 0; 
     } 
}; 

class ElectricCar : public Car { 
    public: 
     ElectricCar() { 
      mSpecialist = new ElectricMaintenance(); 
     } 

     int maintenance(){ 
      mSpecialist->performMaintenance(); 
      return 0; 
     } 
}; 

int _tmain(int argc, _TCHAR* argv[]) { 

    Car *myCar; 

    myCar = new GasolineCar(); 
    myCar->maintenance(); /* I dont know what is involved in maintenance. But, I do know the maintenance has to be performed */ 


    myCar = new ElectricCar(); 
    myCar->maintenance(); 

    return 0; 
} 

附加說明: 你是車主誰擁有多輛。你開發出你想外包的服務。在我們的案例中,我們想要外包所有汽車的維護工作。

  1. 您確定了適用於所有汽車和服務提供商的合同(接口)。
  2. 服務提供商提供了一種提供服務的機制。
  3. 您不必擔心將汽車類型與服務提供商關聯。您只需指定何時計劃維護並調用它。適當的服務公司應該跳入並執行維護工作。

    替代方法。

  4. 您可以確定適用於所有車輛的工作(可以是新界面界面)。
  5. 提出了一種機制來提供服務。基本上你會提供實現。
  6. 您調用該工作並自己動手。在這裏你要做適當的維護工作。

    第二種方法的缺點是什麼? 您可能不是尋找最佳維護方法的專家。你的工作是駕駛汽車並享受它。不是爲了維護它。

    第一種方法的缺點是什麼? 找到一家公司的開銷很大。除非你是一家租車公司,否則可能不值得。