在阅读设计模式时,人们会发现这句话。
但我不明白,有人可以帮我解释一下吗?
答案 0 :(得分:123)
接口只是合同或签名,他们不知道 关于实施的任何事情。
对接口进行编码意味着,客户端代码始终保存由工厂提供的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
}
这只是一个基本的例子 这个原理的实际解释是 超出了这个答案的范围。
我已经更新了上面的示例,并添加了一个抽象的Speaker基类。在此更新中,我向所有Spakers添加了一个功能“SayHello”。所有发言者都说“Hello World”。这是具有类似功能的常见功能。请参阅类图,您会发现Speaker抽象类实现了ISpeaker接口并将Speak()标记为抽象,这意味着每个Speaker实现负责实现Speak方法,因为它因Speaker而异。但是所有发言者都一致地说“你好”。因此,在抽象的Speaker类中,我们定义了一个表示“Hello World”的方法,每个Speaker实现都将派生出SayHello方法。
考虑一下SpanishSpeaker不能说你好的情况,所以在这种情况下你可以覆盖西班牙语说话者的SayHello方法并引发适当的异常。
请注意,我们有 没有对Interface进行任何更改 ISpeaker。和客户端代码和 SpeakerFactory也不受影响 不变。这就是我们通过 Programming-to-Interface 实现的目标。
我们可以通过简单地在每个实现中添加一个基本抽象类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
}
答案 1 :(得分:26)
将接口视为对象与其客户端之间的契约。也就是说,接口指定了对象可以执行的操作,以及用于访问这些内容的签名。
实现是实际行为。比方说,你有一个方法sort()。您可以实现QuickSort或MergeSort。只要接口没有改变,这对调用排序的客户端代码无关紧要。
Java API和.NET Framework等库大量使用接口,因为数百万程序员使用提供的对象。这些库的创建者必须非常小心,他们不会更改这些库中的类的接口,因为它会影响使用该库的所有程序员。另一方面,他们可以根据自己的喜好改变实施方式。
如果作为程序员,您针对实现进行编码,那么一旦更改代码就会停止工作。所以以这种方式考虑界面的好处:
答案 2 :(得分:15)
这意味着您应该尝试编写代码,以便它使用抽象(抽象类或接口)而不是直接实现。
通常,实现通过构造函数或方法调用注入到代码中。因此,您的代码知道接口或抽象类,并可以调用此合同上定义的任何内容。当使用实际对象(接口/抽象类的实现)时,调用正在对象上运行。
这是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
抽象类 - 它提供了一些基础结构,并且重要的是意味着如果您对它进行编码,所有提供程序实现都可以互换使用。
答案 3 :(得分:4)
这句话是关于耦合的。使用面向对象编程的一个潜在原因是重用。因此,例如,您可以在两个协作对象A和B之间拆分算法。这可能对以后创建另一个算法很有用,该算法可能会重用两个对象中的一个或另一个。但是,当这些对象进行通信(发送消息 - 调用方法)时,它们会在彼此之间创建依赖关系。但是如果你想使用一个没有另一个,你需要指定如果我们替换B,其他对象C应该为对象A做什么。这些描述称为接口。这允许对象A在不改变的情况下与依赖于接口的不同对象进行通信。您提到的声明说,如果您计划重用算法的某些部分(或更一般地说是程序),您应该创建接口并依赖它们,这样您可以随时更改具体实现而不更改其他对象如果您使用声明的接口。
答案 4 :(得分: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;
}
补充说明: 您是拥有多辆汽车的车主。你开辟了你想要外包的服务。在我们的案例中,我们希望将所有汽车的维护工作外包出去。
您不必担心将汽车类型与服务提供商相关联。您只需指定何时安排维护并调用它。适当的服务公司应该参与并进行维护工作。
替代方法。
您调用工作并自己完成。在这里,您将完成适当的维护工作。
第二种方法的缺点是什么? 您可能不是找到最佳维护方法的专家。你的工作是驾驶汽车并享受它。不是为了维护它。
第一种方法的缺点是什么? 寻找公司的开销很大。除非您是租车公司,否则可能不值得。
答案 5 :(得分:2)
正如其他人所说,这意味着你的调用代码应该只知道一个抽象的父代,而不是实际工作的实际实现类。
有什么帮助理解这是为什么你应该总是编程到一个界面。原因很多,但最容易解释的有两个原因是
1)测试。
假设我将整个数据库代码放在一个类中。如果我的程序知道具体的类,我只能通过对该类运行它来测试我的代码。我正在使用 - >意思是“与...交谈”。
WorkerClass - > DALClass 但是,让我们为混音添加一个界面。
WorkerClass - > IDAL - > DALClass。
因此DALClass实现了IDAL接口,而worker类只能通过它来调用。
现在,如果我们想为代码编写测试,我们可以创建一个简单的类,就像数据库一样。
WorkerClass - > IDAL - > IFakeDAL。
2)重复使用
按照上面的例子,假设我们想要从SQL Server(我们的具体DALClass使用)迁移到MonogoDB。这将需要主要工作,但如果我们已经编程到接口,那就不行了。在这种情况下,我们只需编写新的DB类,并进行更改(通过工厂)
WorkerClass - > IDAL - > DALClass
到
WorkerClass - > IDAL - > MongoDBClass
答案 6 :(得分:1)
接口描述功能。在编写命令式代码时,请谈谈您正在使用的功能,而不是特定的类型或类。