什么是“程序接口,而不是实现”是什么意思?
在阅读有关设计模式的时候,就会碰到这个短语。
但我不明白,有人能解释一下吗?
接口只是合同或签名,他们不知道任何有关实现。
对接口进行编码意味着,客户端代码始终保存由工厂提供的接口对象。 任何由工厂返回的实例都是类型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 }
替代文字http://ruchitsurati.net/myfiles/interface.png
这只是一个基本的例子,对这个原理的实际解释超出了这个答案的范围。
编辑
我已经更新了上面的例子,并添加了一个抽象的扬声器基类。 在这个更新中,我向所有Spakers添加了一个“SayHello”功能。 所有发言者都会说“Hello World”。 所以这是一个具有相似功能的共同特征。 参考类图,您会发现扬声器抽象类实现了ISpeaker接口,并将Speak()标记为抽象,这意味着每个Speaker实现都负责实施Speak方法,因为它从Speaker到Speaker不同。 但是所有的发言者都一致地说“你好”。 因此,在抽象的Speaker类中,我们定义了一个说“Hello World”的方法,每个Speaker实现将派生出SayHello方法。
考虑一个西班牙语演讲者不能说你好的情况下,所以在这种情况下,你可以重写西班牙语的SayHello方法,并提出适当的例外。
请注意,我们没有对Interface 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 }
将接口看作是对象与客户端之间的契约。 接口指定了对象可以执行的操作,以及访问这些对象的特征。
实现是实际的行为。 举例来说,你有一个方法sort()。 您可以实现QuickSort或MergeSort。 只要接口不改变,对于客户端调用排序应该没有关系。
像Java API和.NET Framework这样的库大量使用接口,因为数百万程序员使用提供的对象。 这些库的创建者必须非常小心,不要将接口更改为这些库中的类,因为这会影响使用该库的所有程序员。 另一方面,他们可以根据自己的喜好改变实施方式。
如果作为一名程序员,您只是在代码停止工作时立即对代码进行编码。 所以想想这个接口的好处:
- 它隐藏了你不需要知道的东西,使对象更简单的使用。
- 它提供了对象如何行为的合约,所以你可以依靠它
这意味着您应该尝试编写代码,以便使用抽象(抽象类或接口)而不是直接实现。
通常通过构造函数或方法调用将实现注入到代码中。 所以,你的代码知道接口或者抽象类,并且可以调用在这个合同上定义的任何东西。 作为一个实际的对象(接口/抽象类的实现)被使用,调用是在对象上进行的。
这是Liskov Substitution Principle
(LSP)的一个子集,是SOLID
原则的L部分。
.NET中的一个例子是用IList
代替List
或Dictionary
,所以你可以在你的代码中使用任何实现IList
类:
// myList can be _any_ object that implements IList public int GetListCount(IList myList) { // Do anything that IList supports return myList.Count(); }
基本类库(BCL)的另一个例子是ProviderBase
抽象类 – 它提供了一些基础结构,同样重要的是,如果你对它进行编码,所有的提供者实现都可以互换使用。
这个陈述是关于耦合的。 使用面向对象编程的一个潜在原因是重用。 因此,例如,您可以将算法分解为两个协作对象A和B.这对于稍后创建另一个算法可能很有用,该算法可能会重用这两个对象中的一个或另一个。 但是,当这些对象进行通信(发送消息 – 调用方法)时,它们会相互创建依赖关系。 但是如果你想使用一个没有另一个,你需要指定如果我们替换B,应该为对象A做些什么其他对象C do。这些描述称为接口。 这允许对象A依赖于接口而不改变地与不同的对象通信。 你提到的陈述说,如果你打算重用算法的一部分(或更一般的程序),你应该创建接口并依赖它们,所以你可以随时改变具体的实现,而不用改变其他对象,如果你使用声明的接口。
如果你要在燃烧汽车时代写一个汽车类,那么你很有可能将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)测试。
比方说,我有我的整个数据库代码在一个类。 如果我的程序知道具体的类,我只能测试我的代码,通过真正运行它反对这个类。 我正在使用 – >来表示“对话”。
WorkerClass – > DALClass但是,让我们添加一个接口的混合。
WorkerClass – > IDAL – > DALClass。
所以DALClass实现了IDAL接口,而worker类只能通过这个来调用。
现在,如果我们想为代码编写测试代码,我们可以创建一个类似数据库的简单类。
WorkerClass – > IDAL – > IFakeDAL。
2)重用
按照上面的例子,假设我们想从SQL Server(我们具体的DALClass使用)移动到MonogoDB。 这将需要大量的工作,但不是如果我们编程到一个接口。 在这种情况下,我们只需编写新的DB类,然后更改(通过工厂)
WorkerClass – > IDAL – > DALClass
至
WorkerClass – > IDAL – > MongoDBClass
接口描述功能。 在编写命令性代码时,请介绍所使用的功能,而不是特定的类型或类。