Quelles sont les bonnes raisons d'utiliser l'implémentation d'interface explicite dans le seul but de masquer les membres?

11

Au cours d'une de mes études sur les subtilités de C #, je suis tombé sur un passage intéressant concernant l'implémentation d'interface explicite.

While this syntax is quite helpful when you need to resolve name clashes, you can use explicit interface implementation simply to hide more "advanced" members from the object level.

La différence entre autoriser l'utilisation object.method()ou exiger le moulage ((Interface)object).method()semble être une obscénité mesquine à mes yeux inexpérimentés. Le texte a noté que ce cachera la méthode de IntelliSense au niveau de l' objet, mais pourquoi voulez - vous voulez faire que s'il n'a pas été nécessaire pour éviter les conflits de nom?

Nathanus
la source

Réponses:

12

Imaginez une situation où une interface force une classe à implémenter des méthodes qui n'ont pas vraiment de sens dans le cadre de la classe. Cela peut souvent se produire lorsque les interfaces sont particulièrement grandes et violent le principe de ségrégation des interfaces.

Cette classe doit implémenter l'interface, mais au lieu de polluer son interface publique très visible avec des méthodes qui n'ont pas de sens, vous pouvez implémenter explicitement les méthodes que vous ne voulez pas être évidemment disponibles. L'interface publique très visible de la classe est la façon dont vous souhaitez que la classe soit utilisée, et l'implémentation explicite est là lorsque la classe est accessible via l'interface.

Chris Pitman
la source
6
Est-ce juste moi, ou est-ce que ça ressemble à une idée terrible?
Kevin Peno
5
@Kevin, comment ça? Parfois, l'interface est hors de votre contrôle et vous devez l'utiliser. Atténuer les dommages d'une interface dominatrice semble raisonnable.
Chris Pitman
1
+1 pour avoir souligné que bien des fois le monde réel fait obstacle à la bonne façon de faire les choses. @ Kevin oui c'est une idée terrible mais parfois vous n'avez pas le choix et vous devez travailler avec le slop - mieux vaut le cacher et faire une façade de faire les choses correctement quand vous ne pouvez pas vraiment le faire correctement.
Wayne Molina
@Wayne, je comprends votre raisonnement pour vouloir / utiliser un tel système. Enfer, je l'emploierais probablement exactement dans le cas que vous dites. Je suppose que mon bref commentaire était vraiment juste de souligner que ce n'est pas correct, alors pourquoi le permettre dans la langue.
Kevin Peno
1
Une séparation d'interface excessive peut être un obstacle majeur à la composition et à l'agrégation. Considérez, par exemple, que l'on souhaite avoir une implémentation IEnumerable<T>qui se comporte comme la concaténation de deux autres. Si elle IEnumerable<T>comprenait une propriété pour dire si son nombre était connu, potentiellement infini, ou aucun des deux, ainsi qu'une méthode pour demander quel était son nombre, une classe qui agrégerait plusieurs IEnumerable<T>et se comporterait comme une concaténation pourrait signaler efficacement son nombre dans le cas où certains de les collections encapsulées connaissaient leurs comptes.
supercat
4

L'application la plus utile que j'ai trouvée concerne l'implémentation d'usines. Dans de nombreux cas, il est utile de créer des classes qui sont modifiables à l'intérieur de l'usine mais immuables aux classes externes. Cela peut être facilement implémenté en Java en utilisant des classes internes, en rendant les champs et leurs setters privés et en exposant uniquement les getters en tant que membres publics. Cependant en C #, j'ai dû utiliser des interfaces explicites pour réaliser la même chose. J'expliquerai plus loin:

En Java, la classe interne ET la classe externe peuvent accéder aux membres privés les unes des autres, ce qui est tout à fait logique car les classes sont très étroitement liées. Ils sont dans le même fichier de code et sont probablement développés par le même développeur. Cela signifie que les champs et méthodes privés de la classe interne sont toujours accessibles par la fabrique pour modifier leurs valeurs. Mais les classes externes ne pourront pas accéder à ces champs sauf via leurs getters publics.

Cependant, en C #, les classes externes ne peuvent pas accéder aux membres privés des classes internes, ce concept n'est donc pas directement applicable. J'ai utilisé une interface explicite comme solution de contournement en définissant une interface privée dans la classe externe et en l'implémentant explicitement dans la classe interne. De cette façon, seule la classe externe peut accéder aux méthodes de cette interface de la même manière que cela se fait en Java (mais elles doivent être des méthodes, pas des champs).

Exemple:

public class Factory
{
    // factory method to create a hard-coded Mazda Tribute car.
    public static Car CreateCar()
    {
        Car car = new Car();

        // the Factory class can modify the model because it has access to
        // the private ICarSetters interface
        ((ICarSetters)car).model = "Mazda Tribute";

        return car;
    }

    // define a private interface containing the setters.
    private interface ICarSetters
    {
        // define the setter in the private interface
        string model { set; }
    }

    // This is the inner class. It has a member "model" that should not be modified
    // but clients, but should be modified by the factory.
    public class Car: ICarSetters
    {
        // explicitly implement the setter
        string ICarSetters.model { set; }

        // create a public getter
        public string model { get; }
    }
}

class Client
{
    public Client()
    {
        Factory.Car car = Factory.CreateCar();

        // can only read model because only the getter is public
        // and ICarSetters is private to Factory
        string model = car.model;
    }
}

C'est pour cela que j'utiliserais des interfaces explicites.

Elias
la source
3

Si une classe implémente deux interfaces qui contiennent un membre avec la même signature, l'implémentation de ce membre sur la classe obligera les deux interfaces à utiliser ce membre comme implémentation. Dans l'exemple suivant, tous les appels pour Paintappeler la même méthode. C #

class Test 
{
    static void Main()

    {
        SampleClass sc = new SampleClass();
        IControl ctrl = (IControl)sc;
        ISurface srfc = (ISurface)sc;

        // The following lines all call the same method.
        sc.Paint();
        ctrl.Paint();
        srfc.Paint();
    }
}


interface IControl
{
    void Paint();
}
interface ISurface
{
    void Paint();
}
class SampleClass : IControl, ISurface
{
    // Both ISurface.Paint and IControl.Paint call this method. 
    public void Paint()
    {
        Console.WriteLine("Paint method in SampleClass");
    }
}

En revanche, l'implémentation explicite de l'un (ou des deux) d'entre eux vous permet de spécifier un comportement différent pour chacun.

Muhammad Tayyeb
la source
1

Les interfaces explicites sont pratiques lorsqu'un objet a deux ou plusieurs formes de communication très distinctes.

Par exemple, un objet qui gère une collection d'objets enfants qui doivent communiquer avec le parent.

Il y a certaines actions que nous voulons seulement accessibles aux appelants externes et certaines actions que nous voulons seulement accessibles aux objets enfants.

Des interfaces explicites contribuent à garantir ce fossé.

    static void Main(string[] args)
    {
        var parent = new Parent();
        parent.DoExternalStuff();
    }

    interface IExternal {
        void DoExternalStuff();
    }

    interface IInternal {
        void DoInternalStuff();
    }

    class Parent : IExternal, IInternal
    {
        public Parent()
        {
            var child = new Child(this);
        }

        public void DoExternalStuff()
        {
           // do something exciting here for external callers
        }

        void IInternal.DoInternalStuff()
        {
            // do something exciting here for internal callers
        }
    }

    class Child
    {
        public Child(IInternal parent)
        {
            parent.DoInternalStuff();
        }
    }
lzcd
la source
0

Peut-être si vous aviez des fonctionnalités utiles aux utilisateurs, mais seulement si vous savez vraiment ce que vous faites (assez pour avoir lu la documentation pour en savoir plus sur la fonction) mais cela risque de casser des choses si vous ne le faites pas.

Pourquoi vous feriez cela plutôt que de fournir, disons une deuxième "interface de fonctionnalités avancées", je ne sais pas.

Malcolm
la source
0

Le code est généralement lu plus qu'il n'est écrit et j'utilise uniquement Intellisense pour écrire du code rapidement. Je n'écrirais pas de code d'une manière particulière juste pour rendre Intellisense plus agréable.

Je ne vois pas particulièrement comment

(Objet (Interface)) .method () est plus lisible que object.method ().

Si j'avais beaucoup de méthodes sur un objet particulier, je pourrais me demander si c'est un objet divin et devrait en fait être divisé en plusieurs objets plus simples.

Peter Smith
la source