On tombe sur cette phrase en lisant sur les modèles de conception.
Mais je ne comprends pas, quelqu'un pourrait-il m'expliquer cela?
On tombe sur cette phrase en lisant sur les modèles de conception.
Mais je ne comprends pas, quelqu'un pourrait-il m'expliquer cela?
Réponses:
Les interfaces ne sont que des contrats ou des signatures et elles ne savent rien des implémentations.
Codage par rapport aux moyens d'interface, le code client contient toujours un objet Interface qui est fourni par une usine. Toute instance retournée par l'usine serait de type Interface que toute classe candidate d'usine doit avoir implémentée. De cette façon, le programme client ne se soucie pas de l'implémentation et la signature de l'interface détermine ce que toutes les opérations peuvent être effectuées. Cela peut être utilisé pour modifier le comportement d'un programme au moment de l'exécution. Il vous aide également à écrire de bien meilleurs programmes du point de vue de la maintenance.
Voici un exemple de base pour vous.
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
}
Ceci n'est qu'un exemple de base et l'explication réelle du principe dépasse le cadre de cette réponse.
J'ai mis à jour l'exemple ci-dessus et ajouté une Speaker
classe de base abstraite . Dans cette mise à jour, j'ai ajouté une fonctionnalité à tous les haut-parleurs pour "SayHello". Tous les orateurs parlent "Hello World". C'est donc une caractéristique commune avec une fonction similaire. Reportez-vous au diagramme de classes et vous constaterez que Speaker
la classe abstraite implémente l' ISpeaker
interface et marque le Speak()
comme abstrait, ce qui signifie que chaque implémentation Speaker est responsable de l'implémentation de la Speak()
méthode car elle varie de Speaker
à Speaker
. Mais tous les orateurs disent "Bonjour" à l'unanimité. Ainsi, dans la classe abstraite Speaker, nous définissons une méthode qui dit "Hello World" et chaque Speaker
implémentation dérivera la SayHello()
méthode.
Prenons un cas où SpanishSpeaker
je ne peux pas dire bonjour, alors dans ce cas, vous pouvez remplacer la SayHello()
méthode pour Spanish Speaker et lever une exception appropriée.
Veuillez noter que nous n'avons apporté aucune modification à l'interface ISpeaker. Et le code client et SpeakerFactory restent inchangés. Et c'est ce que nous réalisons par la programmation à l'interface .
Et nous pourrions obtenir ce comportement en ajoutant simplement une classe abstraite de base Speaker et quelques modifications mineures dans chaque implémentation, laissant ainsi le programme d'origine inchangé. Il s'agit d'une fonctionnalité souhaitée de toute application et elle rend votre application facilement maintenable.
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
comme type, vous pouvez toujours supposer que l'accès aléatoire est rapide en appelant à plusieurs reprises get(i)
.
Pensez à une interface comme un contrat entre un objet et ses clients. C'est l'interface spécifie les choses qu'un objet peut faire, et les signatures pour accéder à ces choses.
Les implémentations sont les comportements réels. Disons par exemple que vous avez une méthode sort (). Vous pouvez implémenter QuickSort ou MergeSort. Cela ne devrait pas avoir d'importance pour le code client appelant sort tant que l'interface ne change pas.
Les bibliothèques comme l'API Java et le .NET Framework font un usage intensif des interfaces car des millions de programmeurs utilisent les objets fournis. Les créateurs de ces bibliothèques doivent faire très attention à ne pas changer l'interface avec les classes de ces bibliothèques car cela affectera tous les programmeurs utilisant la bibliothèque. D'un autre côté, ils peuvent changer la mise en œuvre autant qu'ils le souhaitent.
Si, en tant que programmeur, vous codez par rapport à l'implémentation, dès qu'elle change, votre code cesse de fonctionner. Pensez donc aux avantages de l'interface de cette façon:
Cela signifie que vous devez essayer d'écrire votre code pour qu'il utilise une abstraction (classe abstraite ou interface) au lieu de l'implémentation directement.
Normalement, l'implémentation est injectée dans votre code via le constructeur ou un appel de méthode. Ainsi, votre code connaît l'interface ou la classe abstraite et peut appeler tout ce qui est défini sur ce contrat. Lorsqu'un objet réel (implémentation de l'interface / classe abstraite) est utilisé, les appels opèrent sur l'objet.
Il s'agit d'un sous-ensemble du Liskov Substitution Principle
(LSP), le L des SOLID
principes.
Un exemple dans .NET serait de coder avec IList
au lieu de List
ou Dictionary
, vous pouvez donc utiliser n'importe quelle classe qui implémente de IList
manière interchangeable dans votre code:
// myList can be _any_ object that implements IList
public int GetListCount(IList myList)
{
// Do anything that IList supports
return myList.Count();
}
Un autre exemple de la bibliothèque de classes de base (BCL) est la ProviderBase
classe abstraite - cela fournit une certaine infrastructure et, ce qui est important, signifie que toutes les implémentations de fournisseur peuvent être utilisées de manière interchangeable si vous codez contre elle.
Si vous deviez écrire une classe de voiture à l'époque des voitures à combustion, il y a de fortes chances que vous implémentiez oilChange () dans le cadre de cette classe. Mais, lorsque les voitures électriques sont introduites, vous seriez en difficulté car il n'y a pas de changement d'huile pour ces voitures et aucune mise en œuvre.
La solution au problème est d'avoir une interface performMaintenance () dans la classe Car et de cacher les détails dans une implémentation appropriée. Chaque type de voiture fournirait sa propre implémentation pour performMaintenance (). En tant que propriétaire d'une voiture, tout ce que vous avez à faire est de performMaintenance () et de ne pas vous soucier de vous adapter en cas de CHANGEMENT.
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;
}
Explication supplémentaire: vous êtes un propriétaire de voiture qui possède plusieurs voitures. Vous découpez le service que vous souhaitez externaliser. Dans notre cas, nous voulons externaliser les travaux de maintenance de toutes les voitures.
Vous ne voulez pas vous soucier d'associer le type de voiture au fournisseur de services. Vous spécifiez simplement quand vous souhaitez planifier la maintenance et l'appeler. Une société de service appropriée doit intervenir et effectuer les travaux de maintenance.
Approche alternative.
Vous invoquez le travail et le faites vous-même. Ici, vous allez effectuer le travail de maintenance approprié.
Quel est l'inconvénient de la 2ème approche? Vous n'êtes peut-être pas l'expert pour trouver la meilleure façon d'effectuer la maintenance. Votre travail consiste à conduire la voiture et à en profiter. Ne pas avoir à le maintenir.
Quel est l'inconvénient de la première approche? Il y a des frais généraux liés à la recherche d'une entreprise, etc.
Cette déclaration concerne le couplage. Une des raisons potentielles de l'utilisation de la programmation orientée objet est la réutilisation. Ainsi, par exemple, vous pouvez diviser votre algorithme entre deux objets collaboratifs A et B. Cela peut être utile pour la création ultérieure d'un autre algorithme, qui pourrait réutiliser l'un ou l'autre des deux objets. Cependant, lorsque ces objets communiquent (envoient des messages - méthodes d'appel), ils créent des dépendances entre eux. Mais si vous voulez utiliser l'un sans l'autre, vous devez spécifier ce que doit faire un autre objet C pour l'objet A si nous remplaçons B. Ces descriptions sont appelées interfaces. Cela permet à l'objet A de communiquer sans changement avec un objet différent en fonction de l'interface. La déclaration que vous avez mentionnée dit que si vous prévoyez de réutiliser une partie d'un algorithme (ou plus généralement d'un programme), vous devez créer des interfaces et vous en remettre,
Comme d'autres l'ont dit, cela signifie que votre code appelant ne devrait connaître qu'un parent abstrait, PAS la classe d'implémentation réelle qui fera le travail.
Ce qui aide à comprendre ceci est le POURQUOI vous devez toujours programmer une interface. Il y a de nombreuses raisons, mais deux des plus faciles à expliquer sont
1) Test.
Disons que j'ai tout mon code de base de données dans une classe. Si mon programme connaît la classe concrète, je ne peux tester mon code qu'en l'exécutant réellement sur cette classe. J'utilise -> pour signifier "parle à".
WorkerClass -> DALClass Cependant, ajoutons une interface au mix.
WorkerClass -> IDAL -> DALClass.
Ainsi, DALClass implémente l'interface IDAL, et la classe de travail appelle UNIQUEMENT à travers cela.
Maintenant, si nous voulons écrire des tests pour le code, nous pourrions plutôt créer une classe simple qui agit simplement comme une base de données.
WorkerClass -> IDAL -> IFakeDAL.
2) Réutiliser
En suivant l'exemple ci-dessus, disons que nous voulons passer de SQL Server (que notre DALClass concrète utilise) à MonogoDB. Cela demanderait un travail majeur, mais PAS si nous avons programmé une interface. Dans ce cas, nous écrivons simplement la nouvelle classe DB et changeons (via l'usine)
WorkerClass -> IDAL -> DALClass
à
WorkerClass -> IDAL -> MongoDBClass