2010-04-23 59 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. 您调用该工作并自己动手。在这里你要做适当的维护工作。

    第二种方法的缺点是什么? 您可能不是寻找最佳维护方法的专家。你的工作是驾驶汽车并享受它。不是为了维护它。

    第一种方法的缺点是什么? 找到一家公司的开销很大。除非你是一家租车公司,否则可能不值得。