在阅读有关设计模式的文章时,有人偶然发现了这个短语。
但是我不明白,有人可以帮我解释一下吗?
在阅读有关设计模式的文章时,有人偶然发现了这个短语。
但是我不明白,有人可以帮我解释一下吗?
Answers:
接口只是合同或签名,它们对实现一无所知。
根据接口方式进行编码,客户端代码始终包含一个由工厂提供的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
基类。在此更新中,我向“ SayHello”的所有发言人添加了一项功能。所有发言人都说“ Hello World”。这是具有相似功能的共同特征。参考类图,您会发现Speaker
抽象类实现ISpeaker
接口并将其标记Speak()
为抽象,这意味着每个Speaker实现都负责实现该Speak()
方法,因为它从Speaker
到有所不同Speaker
。但是所有发言者都一致说“你好”。因此,在抽象的Speaker类中,我们定义了一个表示“ Hello World” Speaker
的SayHello()
方法,每个实现都将派生该方法。
考虑一种情况,SpanishSpeaker
您不能说“你好”,那么在这种情况下,您可以覆盖“ SayHello()
西班牙语使用者” 的方法并提出适当的例外。
请注意,我们尚未对Interface 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)
。
将接口视为对象与其客户之间的契约。也就是说,该接口指定对象可以执行的操作,以及用于访问这些操作的签名。
实现是实际的行为。举例来说,您有一个sort()方法。您可以实现QuickSort或MergeSort。只要接口不更改,这对客户端代码调用sort都没有关系。
诸如Java API和.NET Framework之类的库大量使用了接口,因为数百万的程序员使用了提供的对象。这些库的创建者必须非常小心,不要更改这些库中类的接口,因为它将影响使用该库的所有程序员。另一方面,他们可以根据自己的喜好更改实现。
如果作为程序员,您针对实现进行编码,则一旦更改,您的代码就会停止工作。因此,以这种方式考虑接口的好处:
这意味着您应该尝试编写代码,使其使用抽象(抽象类或接口)而不是直接实现。
通常,通过构造函数或方法调用将实现注入到您的代码中。因此,您的代码了解接口或抽象类,并且可以调用此合同上定义的任何内容。当使用实际对象(接口/抽象类的实现)时,调用在该对象上进行。
这是Liskov Substitution Principle
(L)SOLID
原则的(LSP)的子集。
.NET中的示例将使用IList
而不是List
or 进行编码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
抽象类-它提供了一些基础结构,并且重要的是,如果您对它进行编码,则可以互换使用所有提供程序实现。
如果您要在“燃烧汽车”时代编写汽车类,那么您很有可能将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;
}
附加说明:您是拥有多辆车的车主。您决定要外包的服务。在我们的案例中,我们希望将所有汽车的维护工作外包。
您无需担心将汽车类型与服务提供商相关联。您只需要指定何时计划维护并调用它即可。适当的服务公司应介入并执行维护工作。
替代方法。
您可以调用工作并自己完成。在这里,您将进行适当的维护工作。
第二种方法的缺点是什么?您可能不是寻找最佳维护方法的专家。您的工作是驾驶汽车并享受它。不从事维护业务。
第一种方法的缺点是什么?寻找公司等会产生开销。除非您是租车公司,否则可能不值得花大力气。
该声明是关于耦合的。使用面向对象编程的一个潜在原因是重用。因此,例如,您可以将算法分配给两个协作对象A和B。这对于以后创建另一个算法可能很有用,因为后者可能会重用两个对象中的一个或另一个。但是,当这些对象进行通信(发送消息-调用方法)时,它们之间会建立依赖关系。但是,如果要不使用另一个而使用另一个,则需要指定如果替换B,其他对象C应该对对象A做什么。这些描述称为接口。这允许对象A依靠接口与其他对象进行通信而无需更改。您提到的声明说,如果您打算重用算法(或更一般地说是程序)的某些部分,则应创建接口并依赖它们,
就像其他人所说的,这意味着您的调用代码应该只知道抽象父类,而不是将实际工作的实现类。
有助于理解这一点的是为什么您应该始终对接口进行编程。原因很多,但是最容易解释的两个原因是
1)测试。
假设我将整个数据库代码放在一个类中。如果我的程序知道具体的类,则只能通过对该类实际运行它来测试我的代码。我正在使用->表示“交谈”。
WorkerClass-> DALClass但是,让我们为混合添加一个接口。
WorkerClass-> IDAL-> DALClass。
因此DALClass实现了IDAL接口,而工作程序类仅通过此接口进行调用。
现在,如果我们要为代码编写测试,我们可以改为制作一个像数据库一样简单的类。
WorkerClass-> IDAL-> IFakeDAL。
2)重用
按照上面的示例,假设我们要从SQL Server(我们的具体DALClass使用的)转移到MonogoDB。这将需要大量的工作,但是如果我们已经为接口编程,则不会。在这种情况下,我们只需编写新的DB类,然后进行更改(通过工厂)
WorkerClass-> IDAL-> DALClass
至
WorkerClass-> IDAL-> MongoDBClass