Quali sono i buoni motivi per utilizzare l'implementazione esplicita dell'interfaccia al solo scopo di nascondere i membri?


11

Durante uno dei miei studi sulle complessità di C #, mi sono imbattuto in un passaggio interessante riguardante l'implementazione esplicita dell'interfaccia.

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 differenza tra consentire l'uso object.method()o la necessità di lanciare ((Interface)object).method()sembra una sorta di offuscamento mediocre ai miei occhi inesperti. Il testo ha osservato che questo si nasconderà il metodo da Intellisense a livello di oggetto, ma perché te ne vuole fare che, se non era necessario per evitare conflitti di nomi?


Risposte:


12

Immagina una situazione in cui un'interfaccia costringe una classe a implementare metodi che in realtà non hanno senso come parte della classe. Ciò può accadere spesso quando le interfacce sono particolarmente grandi e violano il principio di segregazione dell'interfaccia.

Questa classe deve implementare l'interfaccia, ma invece di inquinare la sua interfaccia pubblica altamente visibile con metodi che non hanno senso, puoi implementare esplicitamente quei metodi che non vuoi essere ovviamente disponibili. L'interfaccia pubblica altamente visibile della classe è il modo in cui si desidera utilizzare la classe e l'implementazione esplicita è presente quando si accede alla classe tramite l'interfaccia.


6
Sono solo io o sembra un'idea terribile?
Kevin Peno,

5
@ Kevin, come mai? A volte l'interfaccia è fuori dal tuo controllo e devi usarla. Mitigare il danno di un'interfaccia prepotente sembra ragionevole.
Chris Pitman,

1
+1 per aver sottolineato che molte volte il mondo reale si oppone al modo giusto di fare le cose. @Kevin sì, è un'idea terribile, ma a volte non hai scelta e devi lavorare con lo slop - meglio nasconderlo e fare una facciata per fare le cose correttamente quando non puoi davvero farlo correttamente.
Wayne Molina,

@Wayne, capisco il tuo ragionamento per voler / usare un tale sistema. Diavolo, probabilmente lo userei esattamente nel caso tu affermi. Immagino che il mio breve commento sia stato solo sottolineando che questo è improprio, quindi perché permetterlo nella lingua.
Kevin Peno,

1
Un'eccessiva separazione dell'interfaccia può essere un grave ostacolo alla composizione e all'aggregazione. Considera, ad esempio, che si desidera avere un'implementazione di IEnumerable<T>cui si comporta come la concatenazione di altri due. Se IEnumerable<T>includesse una proprietà per dire se il suo conteggio era noto, potenzialmente infinito o nessuno dei due, insieme a un metodo per chiedere quale fosse il suo conteggio, una classe che aggrega multipli IEnumerable<T>e si comporta come una concatenazione potrebbe riportare efficacemente il suo conteggio nel caso in cui alcuni dei le collezioni incapsulate conoscevano i loro conteggi.
supercat

4

L'applicazione più utile che ho trovato è con l'implementazione di fabbriche. In molti casi, è utile creare classi mutabili all'interno della fabbrica ma immutabili a classi esterne. Questo può essere facilmente implementato in Java usando le classi interne, rendendo privati ​​i campi e i loro setter ed esponendo solo i getter come membri pubblici. Tuttavia in C #, ho dovuto usare interfacce esplicite per ottenere la stessa cosa. Spiegherò ulteriormente:

In Java, la classe interna E la classe esterna possono accedere a membri privati ​​l'uno dell'altro, il che ha un senso totale poiché le classi sono strettamente correlate. Sono nello stesso file di codice e sono probabilmente sviluppati dallo stesso sviluppatore. Ciò significa che la fabbrica può ancora accedere ai campi e ai metodi privati ​​nella classe interna per modificarne i valori. Ma le classi esterne non saranno in grado di accedere a questi campi se non attraverso i loro getter pubblici.

Tuttavia, in C #, le classi esterne non possono accedere ai membri privati ​​delle classi interne, quindi il concetto non è direttamente applicabile. Ho usato un'interfaccia esplicita come soluzione alternativa definendo un'interfaccia privata nella classe esterna e implementandola esplicitamente nella classe interna. In questo modo, solo la classe esterna può accedere ai metodi in questa interfaccia nello stesso modo in cui viene eseguita in Java (ma devono essere metodi, non campi).

Esempio:

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;
    }
}

Questo è ciò per cui utilizzerei interfacce esplicite.


3

Se una classe implementa due interfacce che contengono un membro con la stessa firma, l'implementazione di quel membro sulla classe farà sì che entrambe le interfacce utilizzino quel membro come loro implementazione. Nel seguente esempio, tutte le chiamate per Paintinvocare lo stesso metodo. 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");
    }
}

Al contrario, l'implementazione esplicita di uno (o entrambi) di essi consente di specificare comportamenti diversi per ciascuno.


1

Le interfacce esplicite sono utili quando un oggetto ha due o più forme di comunicazione molto distinte.

Ad esempio, un oggetto che mantiene una raccolta di oggetti figlio che devono comunicare con il genitore.

Ci sono alcune azioni che vogliamo solo accessibili ai chiamanti esterni e alcune azioni che vogliamo solo accessibili agli oggetti figlio.

Le interfacce esplicite aiutano a garantire questo divario.

    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();
        }
    }

0

Forse se avessi funzionalità utili agli utenti, ma solo se sai davvero cosa stai facendo (abbastanza per aver letto la documentazione per conoscere la funzione) ma è probabile che si rompano le cose se non lo fai.

Perché dovresti farlo piuttosto che fornire, ad esempio una seconda "interfaccia con funzionalità avanzate" non lo so.


0

Il codice viene generalmente letto più di quanto sia scritto e utilizzo Intellisense solo per scrivere codice rapidamente. Non scriverei il codice in un modo particolare solo per rendere più piacevole Intellisense.

Non vedo particolarmente come

((Interface) object) .method () è più leggibile di object.method ().

Se avessi molti metodi su un particolare oggetto, potrei chiedermi se si tratta o meno di un oggetto divino e dovrebbe essere effettivamente suddiviso in più oggetti più semplici.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.