“编程到接口,而不是实现”是什么意思?

在阅读有关设计模式的内容时,人们会偶然发现这个短语。

但我不明白,有人能解释一下吗?

76609 次浏览

这意味着您应该尝试编写代码,使其直接使用抽象(抽象类或接口)而不是实现。

通常,实现是通过构造函数或方法调用注入到代码中的。因此,您的代码了解接口或抽象类,并且可以调用在此契约上定义的任何内容。当使用一个实际的对象(接口/抽象类的实现)时,调用就在对象上进行操作。

这是 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();
}

基类库(Base Class Library,BCL)的另一个例子是 ProviderBase抽象类——它提供了一些基础设施,同样重要的是,如果您根据它编写代码,那么所有提供者实现都可以互换使用。

接口只是契约或签名,他们不知道 任何关于实现的东西

针对接口编码意味着,客户端代码始终保存由工厂提供的 Interface 对象。工厂返回的任何实例都是 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

这只是一个基本的例子 原则的实际解释是 超出了这个答案的范围

剪辑

我已经更新了上面的示例,并添加了一个抽象的 Speaker基类。在这个更新中,我为所有的扬声器添加了一个特性“ SayHello”。所有演讲者请说“你好,世界”。这是具有相似功能的共同特征。参考类图,您会发现 Speaker抽象类实现了 ISpeaker接口,并将 Speak()标记为抽象,这意味着每个扬声器实现负责实现 Speak()方法,因为它在 SpeakerSpeaker之间变化。但所有发言者一致说“你好”。因此,在抽象的扬声器类中,我们定义了一个表示“ Hello World”的方法,并且每个 Speaker实现都将派生出 SayHello()方法。

考虑一种情况,在这种情况下 SpanishSpeaker不能说 Hello,因此您可以覆盖西班牙语使用者的 SayHello()方法并引发适当的异常。

请注意,我们有 没有对接口进行任何更改 还有客户端代码 扬声器工厂也没有受到影响 这就是我们通过 接口编程实现的

我们可以通过简单地添加一个基本的抽象类扬声器和每个实现中的一些小修改来实现这个行为,从而使原始程序保持不变。这是任何应用程序都想要的特性,它使您的应用程序易于维护。

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

这个语句是关于耦合的。使用面向对象编程的一个潜在原因是重用。例如,您可以将算法分割为两个协作对象 A 和 B。这对于以后创建另一个算法可能很有用,这个算法可能会重用这两个对象中的一个或另一个。但是,当这些对象进行通信(发送消息-调用方法)时,它们会在彼此之间创建依赖关系。但是如果你想使用一个而不使用另一个,你需要指定如果我们替换 B,其他对象 C 应该为对象 A 做什么。这些描述称为接口。这允许对象 A 与依赖于接口的不同对象进行通信,而无需更改。您提到的语句说,如果您计划重用算法的某些部分(或者更一般的程序) ,那么您应该创建接口并依赖它们,因此如果您使用声明的接口,您可能随时更改具体的实现而不更改其他对象。

可以将接口看作是对象与其客户机之间的契约。接口指定了对象可以做的事情,以及访问这些事情的签名。

实现是实际的行为。例如,您有一个 sort ()方法。可以实现快速排序或合并排序。只要接口没有更改,这对调用 sort 的客户端代码来说应该无关紧要。

类似 JavaAPI 和。NETFramework 大量使用接口,因为数百万程序员使用所提供的对象。这些库的创建者必须非常小心,不要更改这些库中类的接口,因为这会影响所有使用库的程序员。另一方面,他们可以随心所欲地更改实现。

如果,作为一个程序员,您根据实现编写代码,那么一旦实现更改您的代码,您的代码就会停止工作。所以这样来考虑界面的好处:

  1. 它隐藏了你不需要知道的东西,使对象更容易使用。
  2. 它提供了对象行为的契约,因此您可以依赖它

当编写命令式代码时,谈论你正在使用的功能,而不是特定的类型或类。

正如其他人所说,这意味着您的调用代码应该只知道一个抽象父类,而不是实际的实现类。

什么有助于理解这是为什么你应该总是编程到一个接口。原因有很多,但最容易解释的两个是

1)测试。

假设我将整个数据库代码放在一个类中。如果我的程序知道具体的类,我只能通过在该类上运行代码来测试它。我用-> 来表示“谈话”。

WorkerClass-> DALClass 然而,让我们为这个组合添加一个接口。

WorkerClass-> IDAL-> DALClass.

因此 DALClass 实现 IDAL 接口,而工作类仅通过此调用。

现在,如果我们想为代码编写测试,我们可以创建一个简单的类,它的作用就像一个数据库。

WorkerClass-> IDAL-> IFakeDAL.

2)循环再用

根据上面的例子,假设我们想从 SQLServer (我们具体的 DALClass 使用的)移动到 MonogoDB。这将采取主要的工作,但不是如果我们已经编程到一个接口。在这种情况下,我们只需编写新的 DB 类,然后修改(通过工厂)

WorkerClass-> IDAL-> DALClass

WorkerClass-> IDAL-> MongoDBClass

如果你要编写一个汽车燃烧时代的类,然后有一个很大的机会,你会实现这个类的一部分,油改变()。但是,当电动汽车介绍,你会遇到麻烦,因为没有涉及到这些汽车的油改变,并没有实施。

这个问题的解决方案是在 Car 类中有一个 PerformVienna ()接口,并在适当的实现中隐藏详细信息。每种 Car 类型都将提供其自己的 PerformVienna ()实现。作为一个汽车的拥有者,你所要面对的只是性能维护() ,而不用担心在发生变化时进行适应。

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. 您可以调用这项工作并自己完成它。在这里,您将完成适当的维护工作。

    第二种方法的缺点是什么? 您可能不是寻找最佳维护方法的专家。你的工作就是开车,享受它。而不是去维护它。

    第一种方法的缺点是什么? 找一家公司的费用等等。除非你是一家汽车租赁公司,否则这种努力可能不值得。