阅读设计模式相关内容时,我们会经常遇到这个短语。
但我不理解它的含义,有人能为我解释一下吗?
阅读设计模式相关内容时,我们会经常遇到这个短语。
但我不理解它的含义,有人能为我解释一下吗?
接口只是契约或签名,它们不知道任何实现细节。
使用接口编码意味着客户端代码始终持有由工厂提供的接口对象。工厂返回的任何实例都将是类型为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
}
这只是一个基本示例,原则的实际解释超出了本答案的范围。
我已经更新了上面的示例并添加了一个抽象的Speaker
基类。在此更新中,我为所有扬声器添加了一个功能:“SayHello”。所有扬声器都会说“Hello World”。因此,这是一个具有相似功能的共同特征。请参考类图,您会发现Speaker
抽象类实现了ISpeaker
接口,并将Speak()
标记为抽象,这意味着每个扬声器实现负责实现Speak()
方法,因为它因Speaker
而异。但是所有扬声器一致地说“Hello”。因此,在抽象Speaker类中,我们定义了一个方法,用于表示“Hello World”,并且每个Speaker
实现将派生SayHello()
方法。
考虑这样一种情况:如果SpanishSpeaker
无法说“Hello”,那么您可以覆盖SpanishSpeaker
的SayHello()
方法并引发适当的异常。
请注意,我们未对接口ISpeaker进行任何更改。客户端代码和SpeakerFactory也保持不变。这就是我们通过“面向接口编程”所实现的。
我们可以通过简单地添加一个基本的抽象类Speaker以及在每个实现中进行一些微小的修改来实现此行为,从而使原始程序保持不变。这是任何应用程序的期望特性,它使您的应用程序易于维护。
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
}
List
作为类型,你仍然可能会假设通过多次调用 get(i)
来实现快速的随机访问。 - Joachim SauerList aList = new ArrayList()
,则是针对List
接口进行编码,而不是针对ArrayList
实现(在此使用Java)。更重要的是,如果您使用没有单独接口的类,例如StringBuilder bldr = new StringBuilder()
(它实现了CharSequence
接口,但我们可以忽略这一点),您仍然是在针对接口进行编码,而不是实现(StringBuilder
类的隐式公共接口)。 - Rogério将接口视为对象与其客户端之间的契约。即接口指定了对象可以做什么以及访问这些内容的签名。
实现是实际的行为。例如,您有一个方法sort(),您可以实现QuickSort或MergeSort。只要接口不改变,调用sort的客户端代码就不会受到影响。
像Java API和.NET Framework这样的库大量使用接口,因为有数百万程序员使用提供的对象。这些库的创建者必须非常小心,不要更改这些库中的类的接口,因为这将影响所有使用该库的程序员。另一方面,他们可以随意更改实现方式。
如果作为程序员,您编写针对实现的代码,则一旦实现更改,您的代码将停止工作。因此,考虑接口的好处:
Liskov替换原则
(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();
}
ProviderBase
抽象类 - 它提供了一些基础设施,并且同样重要的是,如果您在其上编写代码,则所有提供程序实现都可以互换使用。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;
}
第一种方法的缺点是什么? 需要花费时间寻找公司等开销。除非您是租车公司,否则可能不值得这样做。
接口描述了能力。在编写命令式代码时,要谈论你正在使用的能力,而不是特定的类型或类。