Que signifie "programme aux interfaces, pas aux implémentations"?

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?

97
demandé sur andrew.fox 2010-04-23 14:27:04

7 réponses

Les Interfaces ne sont que des contrats ou des signatures et elles ne savent pas rien sur les implémentations.

Le codage par rapport à l'interface signifie que le code client contient toujours un objet D'Interface fourni par une usine. Toute instance renvoyé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 s'inquiète pas de la mise en œuvre et la signature de l'interface détermine ce que toutes les opérations peut être fait. Ceci 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
}

Le texte d'Alt http://ruchitsurati.net/myfiles/interface.png

C'est juste un exemple de base et l'explication réelle du principe est au-delà de la portée de cette réponse.

Modifier

J'ai mis à jour l'exemple ci-dessus et ajouté une classe de base de haut-parleur abstrait. Dans cette mise à jour, j'ai ajouté une fonctionnalité à tous les Spakers à "SayHello". Tous les orateurs parlent "Hello World". C'est donc une caractéristique commune avec une fonction similaire. Reportez-vous au diagramme de classe et vous trouverez que la classe abstraite Speaker implémente l'interface ISpeaker et marque Speak() comme abstraite, ce qui signifie que l'implémentation de each Speaker est responsable de l'implémentation de la méthode Speak car elle varie d'un haut-parleur à L'autre. Mais tous les orateurs disent " bonjour" unanimement. Donc, dans la classe Abstract Speaker, nous définissons une méthode qui dit "Hello World" et chaque implémentation de Speaker dérivera la méthode SayHello.

Considérons un cas où SpanishSpeaker ne peut pas dire bonjour, donc dans ce cas, vous pouvez remplacer la méthode SayHello pour Spanish Speaker et déclencher une exception appropriée.

Veuillez noter que nous avons pas apporté de modifications à L'Interface ISpeaker. Et le code client et SpeakerFactory restent également inchangés inchangé. Et c'est ce que nous réalisons par programmation-Interface.

Et nous pourrions réaliser ce comportement en ajoutant simplement un haut-parleur de classe abstraite de base et une modification mineure dans chaque implémentation laissant ainsi le programme original inchangé. Ceci est une caractéristique souhaitée de toute application et il 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
}

Le texte d'Alt http://demo.ruchitsurati.net/myfiles/interface1.png

112
répondu this. __curious_geek 2010-07-28 10:30:31

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 d'accès à 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 le tri tant que l'interface ne change pas.

Les bibliothèques comme L'API Java et le Framework. net rendent lourd utilisation d'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 pour les classes de ces bibliothèques car cela affectera tous les programmeurs utilisant la bibliothèque. D'autre part, ils peuvent changer la mise en œuvre autant qu'ils le souhaitent.

Si, en tant que programmeur, vous codez contre l'implémentation, dès qu'elle change, votre code cesse de fonctionner. Alors pensez aux avantages de l'interface de cette façon:

  1. Il cache les choses que vous n'avez pas besoin de savoir rendant l'objet plus simple à utiliser.
  2. Il fournit le contrat de la façon dont l'objet se comportera afin que vous puissiez en dépendre
26
répondu Vincent Ramdhanie 2011-11-16 04:36:33

Cela signifie que vous devriez essayer d'écrire votre code afin 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. Comme un objet réel (implémentation de la classe interface/abstract) est utilisé, les appels fonctionnent sur le objet.

Il s'agit d'un sous-ensemble de Liskov Substitution Principle (LSP), le L du SOLID principes.

Un exemple dans. NET serait de coder avec IList au lieu de List ou Dictionary, de sorte que vous pouvez utiliser n'importe quelle classe qui implémente IList de 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 ProviderBase classe abstraite-cela fournit une infrastructure, et signifie aussi important que toutes les implémentations du fournisseur peuvent être utilisées interchangeable si vous coder contre elle.

14
répondu Oded 2010-04-23 11:37:57

Cette déclaration concerne le couplage. Une raison potentielle d'utiliser la programmation orientée objet est la réutilisation. Ainsi, par exemple, vous pouvez diviser votre algorithme entre deux objets collaborateurs A et B. Cela pourrait ê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 (envoyer 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 devrait 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 s'appuyant sur l'interface. L'instruction que vous avez mentionnée indique que si vous envisagez de réutiliser une partie d'un algorithme (ou plus généralement un programme), vous devez créer des interfaces et compter sur elles, de sorte que vous pouvez changer l'implémentation concrète à tout moment sans changer d'autres objets si vous utilisez l'interface déclarée.

4
répondu Gabriel Ščerbák 2010-04-23 10:33:34

Si vous deviez écrire une classe Car à L'ère de la Combustion-Car, il y a de grandes 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 impliqué pour ces voitures, et aucune implémentation.

La solution au problème est d'avoir une interface performMaintenance () dans la classe Car et de masquer les détails dans l'implémentation appropriée. Chaque type de voiture fournirait sa propre mise en œuvre pour performMaintenance (). En tant que propriétaire d'une voiture, tout ce que vous avez à faire est performMaintenance() et ne pas vous soucier de l'adaptation quand il y a un 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 taillez le service que vous voulez externaliser. Dans notre cas, nous voulons externaliser les travaux d'entretien de toutes les voitures.

  1. vous identifiez le contrat (Interface) qui convient à toutes vos voitures et fournisseurs de services.
  2. les fournisseurs de services viennent avec un mécanisme pour fournir le service.
  3. Vous ne voulez pas vous soucier d'associer le type de voiture avec le fournisseur de services. Vous spécifiez simplement quand vous voulez planifier la maintenance et l'invoquer. Société de service appropriée devrait sauter et effectuer les travaux d'entretien.

    Autre approche.

  4. vous identifiez le travail (peut être une nouvelle Interface D'interface) qui tient bon pour toutes vos voitures.
  5. Vous sortez avec un mécanisme pour fournir le service. Fondamentalement, vous allez fournir la mise en oeuvre.
  6. Vous invoquez le travail et faites-le vous-même. Ici, vous allez faire le travail de travaux d'entretien appropriés.

    Quel est l'inconvénient de la 2ème approche? Vous ne pouvez pas être l'expert à trouver la meilleure façon d'en faire l'entretien. Votre travail consiste à conduire la voiture et en profiter. Ne pas être dans les affaires de le maintenir.

    Qu'est-ce que l'inconvénient de la première approche? Il est la surcharge de trouver une entreprise etc. Sauf si vous êtes une entreprise de location de voitures, il peut ne pas être la peine.

4
répondu Raghav Navada 2014-03-11 05:46:54

Comme d'autres l'ont dit, cela signifie que votre code appelant ne doit connaître qu'un parent abstrait, pas la classe d'implémentation réelle qui fera le travail.

Ce qui aide à comprendre ceci est la raison pour laquelle vous devriez toujours programmer sur une interface. Il y a plusieurs 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 concrete, Je ne peux tester mon code qu'en l'exécutant vraiment contre cette classe. J'utilise -> pour signifier "parle à".

WorkerClass - > DALClass Cependant, ajoutons une interface au mélange.

WorkerClass - > IDAL - > DALClass.

Donc, la classe DALClass implémente L'interface IDAL, et la classe worker n'appelle que via ceci.

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

Suivant le exemple ci-dessus, disons que nous voulons passer de SQL Server (que notre dalclass béton utilise) à MonogoDB. Cela prendrait 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

2
répondu Mathieson 2013-10-29 21:46:43

Les Interfaces décrivent les capacités. lorsque vous écrivez du code impératif, parlez des capacités que vous utilisez plutôt que de types ou de classes spécifiques.

1
répondu rektide 2010-09-23 23:30:13