“界面的程序,而不是实现”是什么意思?
-
01-10-2019 - |
题
在阅读有关设计模式时,一个偶然发现了这句话。
但是我不明白,有人可以为我解释吗?
解决方案
接口只是合同或签名,他们对实施一无所知。
针对接口表示编码,客户端代码始终保存由工厂提供的接口对象。工厂返回的任何实例都是任何工厂候选类必须实现的类型接口。这样,客户端程序就不必担心实现,并且接口签名确定可以完成所有操作。这可以用于在运行时更改程序的行为。它还可以帮助您从维护角度编写更好的程序。
这是您的基本示例。
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
}
这只是一个基本示例,对原理的实际解释超出了本答案的范围。
编辑
我已经更新了上面的示例,并添加了一个抽象的扬声器基类。在此更新中,我向所有Spakers添加了一个功能,以“ Sayhello”。所有演讲者都说“ Hello World”。因此,这是具有相似功能的常见功能。请参阅类图,您会发现说话者抽象类实施ISPEAKER接口,并将Speak()标记为摘要,这意味着每个说话者实现都负责实现说话方法,因为它因扬声器而异。但是所有演讲者都一致说“你好”。因此,在抽象的演讲者类中,我们定义了一种说“ Hello World”的方法,每个说话者实施将得出Sayhello方法。
考虑一个案例,西班牙语者不能打招呼,因此在这种情况下,您可以覆盖西班牙语者的Sayhello方法并提出适当的例外。
请注意,我们没有对Ispeaker接口进行任何更改。客户代码和发言人也保持不变。这就是我们实现的 编程到接口.
而且,我们可以通过简单地在每个实现中添加基本的抽象类扬声器和一些较小的修改来实现这种行为,从而使原始程序保持不变。这是任何应用程序的所需功能,它使您的应用程序易于维护。
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
}
其他提示
将界面视为对象与其客户之间的合同。那是指定对象可以做的事情以及访问这些东西的签名的界面。
实现是实际行为。例如,您有一个方法stort()。您可以实现QuickSort或Mergesort。只要接口不更改,就对客户端代码调用排序无关紧要。
诸如Java API和.NET框架之类的库可以大量使用接口,因为数百万程序员使用提供的对象。这些库的创建者必须非常小心,以免它们将界面更改为这些库中的类,因为它将使用库影响所有程序员。另一方面,他们可以尽可能多地更改实施。
如果作为程序员,您可以反对实施代码,那么一旦更改代码就停止工作。因此,以这种方式考虑界面的好处:
- 它隐藏了您不需要知道使对象更简单使用的内容。
- 它提供了对象的行为方式的合同,因此您可以依靠
这意味着您应该尝试编写代码,以便使用抽象(抽象类或接口)而不是直接实现。
通常,该实现将通过构造函数或方法调用注入您的代码。因此,您的代码知道接口或抽象类,并可以调用本合同中定义的任何内容。作为实际对象(使用接口/摘要类的实现),调用在对象上运行。
这是 Liskov Substitution Principle
(lsp),l SOLID
原则。
.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
摘要类 - 这提供了一些基础架构,重要的是意味着如果您对其进行编码,则可以互换使用所有提供商。
如果您要在燃烧汽车时代写汽车课,那么您很有可能将OilChange()作为此类的一部分。但是,当引入电动汽车时,您会遇到麻烦,因为这些汽车不涉及石油变化,也没有实施。
解决问题的解决方案是在汽车类中拥有一个permanthending()接口,并在适当的实现中隐藏详细信息。每种汽车类型都将为Pertricmention()提供自己的实现。作为汽车的所有者,您必须处理的只是performenting(),而不必担心在发生更改时适应。
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中将算法分开。这对于以后创建另一种算法可能很有用,这可能会重复使用两个对象中的一个或另一个。但是,当这些对象通信(发送消息 - 调用方法)时,它们会彼此之间创建依赖关系。但是,如果您想使用一个没有另一个的一个,则需要指定某些对象C对object A如果替换B。这些描述称为接口。这允许对象a在不使用不同对象的情况下进行通信。您提到的语句说,如果您打算重复使用算法的某些部分(或更一般的程序),则应创建接口并依靠它们,因此您可以随时更改混凝土实现,而无需更改其他对象声明接口。
正如其他人所说的那样,这意味着您的呼叫代码只能知道抽象父母,而不是可以完成工作的实际实施类。
有助于理解这一点的原因是为什么您应该始终将其编程到接口。有很多原因,但最简单的解释是
1)测试。
假设我将整个数据库代码在一个类中。如果我的程序知道混凝土类,我只能通过对该类真正运行代码来测试我的代码。我正在使用 - >表示“与之交谈”。
但是,workerclass-> dalclass但是,让我们在混合物中添加一个接口。
workerclass-> iDal-> dalclass。
因此,Dalclass实现了IDAL接口,并且Worker类仅通过此呼叫。
现在,如果我们想为代码编写测试,我们可以做一个简单的类,就像数据库一样。
workerclass-> iDal-> ifakedal。
2)重用
在上面的示例之后,假设我们要从SQL Server(我们的混凝土Dalclass使用)转移到MonoGodB。这将需要大量工作,但如果我们已经编程到界面,则不会进行重大工作。在这种情况下,我们只编写新的DB类,然后更改(通过工厂)
WorkerClass-> iDal-> Dalclass
到
WorkerClass-> iDal-> mongodbclass
界面描述功能。在编写命令式代码时,请讨论您使用的功能,而不是特定类型或类。