Po co jawnie implementować interfejs?

122

Więc jaki dokładnie jest dobry przypadek użycia do jawnego zaimplementowania interfejsu?

Czy tylko dlatego, że ludzie używający tej klasy nie muszą patrzeć na wszystkie te metody / właściwości w intelisense?

Mistrzowska moralność
źródło

Odpowiedzi:

146

Jeśli zaimplementujesz dwa interfejsy, oba z tą samą metodą i różnymi implementacjami, musisz zaimplementować je jawnie.

public interface IDoItFast
{
    void Go();
}
public interface IDoItSlow
{
    void Go();
}
public class JustDoIt : IDoItFast, IDoItSlow
{
    void IDoItFast.Go()
    {
    }

    void IDoItSlow.Go()
    {
    }
}
Iain
źródło
Tak, dokładnie to jest jeden przypadek, który rozwiązuje EIMI. A inne kwestie są objęte odpowiedzią „Michael B”.
zaszyfrowany
10
Doskonały przykład. Uwielbiam interfejs / nazwy klas! :-)
Brian Rogers
11
Nie podoba mi się to, dwie metody z tym samym podpisem w klasie, które robią bardzo różne rzeczy? Jest to niezwykle niebezpieczna rzecz i najprawdopodobniej spowoduje spustoszenie w każdym dużym rozwoju. Jeśli masz taki kod, powiedziałbym, że twoja analiza i projekt są na topie.
Mick
4
@Mike Interfejsy mogą należeć do jakiegoś API lub do dwóch różnych API. Może miłość jest tutaj trochę przesadzona, ale przynajmniej byłbym zadowolony, że dostępna jest wyraźna implementacja.
TobiMcNamobi
@BrianRogers I nazwy metod też ;-)
Sнаđошƒаӽ
66

Warto ukryć niepreferowanego członka. Na przykład, jeśli zaimplementujesz oba IComparable<T>i IComparablezazwyczaj lepiej jest ukryć IComparableprzeciążenie, aby nie dawać ludziom wrażenia, że ​​możesz porównać obiekty różnych typów. Podobnie, niektóre interfejsy nie są zgodne z CLS, na przykład IConvertible, jeśli nie zaimplementujesz interfejsu w sposób jawny, użytkownicy końcowi języków, które wymagają zgodności z CLS, nie będą mogli używać twojego obiektu. (Co byłoby bardzo katastrofalne, gdyby implementatorzy BCL nie ukryli elementów IConvertible elementów pierwotnych :))

Inną interesującą uwagą jest to, że normalne użycie takiej konstrukcji oznacza, że ​​struktura, która jawnie implementuje interfejs, może wywołać je tylko poprzez umieszczenie w polu typu interfejsu. Możesz to obejść, używając ogólnych ograniczeń:

void SomeMethod<T>(T obj) where T:IConvertible

Nie zapakuje int, gdy przekażesz je do niego.

Michael B.
źródło
1
Masz literówkę w swoim ograniczeniu. Aby wyjaśnić, powyższy kod działa. Musi znajdować się w początkowej deklaracji podpisu metody w interfejsie. Oryginalny post tego nie określał. Prawidłowy format to także „void SomeMehtod <T> (T obj), gdzie T: IConvertible. Zauważ, że między„) ”a„ gdzie ”nie powinno być dodatkowego dwukropka. Nadal +1 dla sprytnego użycia leki generyczne, aby uniknąć drogiego boksu
Zack Jannsen
1
Cześć, Michael B. Dlaczego więc w implementacji stringa w .NET istnieje publiczna implementacja IComparable: public int CompareTo (wartość obiektu) {if (wartość == null) {return 1; } if (! (wartość to String)) {rzut nowy ArgumentException (Environment.GetResourceString ("Arg_MustBeString")); } return String.Compare (this, (String) wartość, StringComparison.CurrentCulture); } Dzięki!
zzfima
stringistniało przed lekami generycznymi i ta praktyka była w modzie. Kiedy pojawił się .net 2, nie chcieli złamać publicznego interfejsu, stringwięc zostawili go tak, jak był, z zabezpieczeniem na miejscu.
Michael B
37

Kilka dodatkowych powodów, aby jawnie zaimplementować interfejs:

kompatybilność wsteczna : w przypadku ICloneablezmiany interfejsu, implementujące elementy klasy metod nie muszą zmieniać ich sygnatur metod.

czystszy kod : wystąpi błąd kompilatora, jeśli Clonemetoda zostanie usunięta z ICloneable, jednak jeśli zaimplementujesz metodę niejawnie, możesz skończyć z nieużywanymi `` osieroconymi '' metodami publicznymi

silne typowanie : aby zilustrować historię supercat na przykładzie, byłby to mój preferowany przykładowy kod, implementacja ICloneablejawnie pozwala Clone()na wyraźne wpisywanie, gdy wywołujesz go bezpośrednio jako element MyObjectczłonkowski instancji:

public class MyObject : ICloneable
{
  public MyObject Clone()
  {
    // my cloning logic;  
  }

  object ICloneable.Clone()
  {
    return this.Clone();
  }
}
Wiebe Tijsma
źródło
W tym przypadku wolałbym interface ICloneable<out T> { T Clone(); T self {get;} }. Zauważ, że celowo nie ma ICloneable<T>ograniczenia dla T. Podczas gdy obiekt można ogólnie bezpiecznie sklonować tylko wtedy, gdy jego podstawa może być, można chcieć wyprowadzić z klasy bazowej, którą można bezpiecznie sklonować, obiekt klasy, która nie może. Aby to umożliwić, odradzałbym posiadanie klas dziedziczonych, które ujawniają metodę klonowania publicznego. Zamiast tego mają klasy dziedziczone z protectedmetodą klonowania i zapieczętowane klasy, które z nich pochodzą i ujawniają klonowanie publiczne.
supercat
Jasne, byłoby fajniej, z wyjątkiem tego, że nie ma kowariantnej wersji ICloneable w BCL, więc musiałbyś ją utworzyć, prawda?
Wiebe Tijsma
Wszystkie 3 przykłady zależą od mało prawdopodobnych sytuacji i najlepszych praktyk w zakresie interfejsów.
MickyD,
13

Inną przydatną techniką jest to, aby publiczna implementacja metody funkcji zwracała wartość, która jest bardziej szczegółowa niż określona w interfejsie.

Na przykład obiekt może być implementowany ICloneable, ale jego publicznie widoczna Clonemetoda zwraca swój własny typ.

Podobnie, IAutomobileFactorymoże mieć Manufacturemetodę, która zwraca an Automobile, ale a FordExplorerFactory, która implementuje IAutomobileFactory, może mieć Manufacturemetodę zwracającą a FordExplorer(która pochodzi od Automobile). Kod, który wie, że FordExplorerFactorymoże użyć FordExplorerwłaściwości specyficznych dla obiektu zwróconego przez a FordExplorerFactorybez konieczności typowania, podczas gdy kod, który tylko wiedział, że ma jakiś typ IAutomobileFactory, po prostu poradziłby sobie z jego powrotem jako Automobile.

superkat
źródło
2
+1 ... byłoby to moje preferowane użycie jawnych implementacji interfejsu, chociaż mały przykład kodu byłby prawdopodobnie nieco bardziej przejrzysty niż ta historia :)
Wiebe Tijsma
7

Jest to również przydatne, gdy masz dwa interfejsy z tą samą nazwą członka i podpisem, ale chcesz zmienić jego zachowanie w zależności od tego, jak jest używany. (Nie polecam pisania kodu w ten sposób):

interface Cat
{
    string Name {get;}
}

interface Dog
{
    string Name{get;}
}

public class Animal : Cat, Dog
{
    string Cat.Name
    {
        get
        {
            return "Cat";
        }
    }

    string Dog.Name
    {
        get
        {
            return "Dog";
        }
    }
}
static void Main(string[] args)
{
    Animal animal = new Animal();
    Cat cat = animal; //Note the use of the same instance of Animal. All we are doing is picking which interface implementation we want to use.
    Dog dog = animal;
    Console.WriteLine(cat.Name); //Prints Cat
    Console.WriteLine(dog.Name); //Prints Dog
}
vcsjones
źródło
60
najdziwniejszy przykład związany z OO, jaki kiedykolwiek widziałem:public class Animal : Cat, Dog
mbx
38
@mbx: Gdyby Animal również zaimplementował Parrot, byłoby to zwierzę zmieniające się w Polly.
RenniePet
3
Pamiętam postać z kreskówek, która była Kotem na jednym końcu i Psem na drugim końcu ;-)
George Birbilis,
2
był serial telewizyjny w latach 80-tych… „Manimal”… gdzie człowiek mógł przekształcić się w… o nieważne
bkwdesign
Morkie wyglądają trochę jak koty i są też jak koty ninja.
samis
6

Może zachować czystość interfejsu publicznego, aby jawnie zaimplementować interfejs, tj. Twoja Fileklasa może zaimplementować IDisposablejawnie i zapewnić publiczną metodę, Close()która może mieć większy sens dla konsumenta niż Dispose().

F # oferuje tylko jawną implementację interfejsu, więc zawsze musisz rzutować na konkretny interfejs, aby uzyskać dostęp do jego funkcjonalności, co powoduje bardzo wyraźne (bez zamierzonego kalambury) użycie interfejsu.

Rozbite szkło
źródło
Myślę, że większość wersji VB obsługiwała również tylko jawne definicje interfejsów.
Gabe
1
@ Gabe - to jest bardziej subtelne niż w przypadku VB - nazewnictwo i dostępność członków, którzy implementują interfejs, są niezależne od wskazania, że ​​są częścią implementacji. Więc w VB i patrząc na odpowiedź @ Iaina (aktualna najlepsza odpowiedź), możesz zaimplementować IDoItFast i IDoItSlow z publicznymi członkami, odpowiednio, „GoFast” i „GoSlow”.
Damien_The_Unbeliever
2
Nie podoba mi się twój konkretny przykład (IMHO, jedyne, co powinno się ukrywać, Disposeto te, które nigdy nie będą wymagały czyszczenia); lepszym przykładem byłoby coś w rodzaju implementacji niezmiennej kolekcji IList<T>.Add.
supercat
5

Jeśli masz wewnętrzny interfejs i nie chcesz publicznie implementować członków swojej klasy, zaimplementowałbyś je jawnie. Niejawne implementacje muszą być publiczne.

Mike Dour
źródło
Ok, to wyjaśnia, dlaczego projekt nie zostałby skompilowany z niejawną implementacją.
pauloya
4

Innym powodem jawnej implementacji jest łatwość konserwacji .

Kiedy klasa staje się „zajęta” - tak, zdarza się, nie wszyscy mamy luksus refaktoryzacji kodu innych członków zespołu - wtedy posiadanie jawnej implementacji jasno pokazuje, że istnieje metoda spełniająca warunki kontraktu interfejsu.

Więc poprawia "czytelność" kodu.

h bob
źródło
IMHO ważniejsze jest, aby zdecydować, czy klasa powinna, czy nie powinna ujawniać metody swoim klientom. To decyduje, czy ma być jawne, czy niejawne. Aby udokumentować, że kilka metod pasuje do siebie, w tym przypadku, ponieważ spełniają one warunki kontraktu - to po to #region, z odpowiednim ciągiem tytułu. I komentarz do metody.
ToolmakerSteve
1

Inny przykład podaje System.Collections.Immutable, w którym autorzy zdecydowali się na użycie tej techniki, aby zachować znajomy interfejs API dla typów kolekcji, jednocześnie usuwając części interfejsu, które nie mają znaczenia dla ich nowych typów.

Konkretnie ImmutableList<T>implementuje, IList<T>a tym samym ICollection<T>( w celu ułatwienia ImmutableList<T>stosowania ze starszym kodem), ale void ICollection<T>.Add(T item)nie ma sensu dla a ImmutableList<T>: ponieważ dodanie elementu do niezmiennej listy nie może zmieniać istniejącej listy, ImmutableList<T>również wywodzi się z IImmutableList<T>którego IImmutableList<T> Add(T item)można użyć do niezmienne listy.

Tak więc w przypadku Addimplementacji ImmutableList<T>ostatecznie wygląda to następująco:

public ImmutableList<T> Add(T item)
{
    // Create a new list with the added item
}

IImmutableList<T> IImmutableList<T>.Add(T value) => this.Add(value);

void ICollection<T>.Add(T item) => throw new NotSupportedException();

int IList.Add(object value) => throw new NotSupportedException();
zepsuty
źródło
0

W przypadku jawnie zdefiniowanych interfejsów wszystkie metody są automatycznie prywatne, nie można nadać im publicznego modyfikatora dostępu. Przypuszczać:

interface Iphone{

   void Money();

}

interface Ipen{

   void Price();
}


class Demo : Iphone, Ipen{

  void Iphone.Money(){    //it is private you can't give public               

      Console.WriteLine("You have no money");
  }

  void Ipen.Price(){    //it is private you can't give public

      Console.WriteLine("You have to paid 3$");
  }

}


// So you have to cast to call the method


    class Program
    {
        static void Main(string[] args)
        {
            Demo d = new Demo();

            Iphone i1 = (Iphone)d;

            i1.Money();

            ((Ipen)i1).Price();

            Console.ReadKey();
        }
    }

  // You can't call methods by direct class object
Md Shahriar
źródło
1
„wszystkie metody są automatycznie prywatne” - nie jest to technicznie poprawne, b / c, jeśli byłyby w rzeczywistości prywatne, nie można by ich było w ogóle wywołać, rzucając lub nie.
samis
0

W ten sposób możemy stworzyć Jawny Interfejs: Jeśli mamy 2 interfejsy i oba interfejsy mają tę samą metodę i jedną klasę, dziedziczą te 2 interfejsy, więc kiedy wywołujemy jedną metodę interfejsu, kompilator pomylił się, którą metodę wywołać, więc możemy Zarządzaj tym problemem za pomocą interfejsu Explicit. Oto jeden przykład, który podałem poniżej.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace oops3
{
    interface I5
    {
        void getdata();    
    }
    interface I6
    {
        void getdata();    
    }

    class MyClass:I5,I6
    {
        void I5.getdata()
        {
           Console.WriteLine("I5 getdata called");
        }
        void I6.getdata()
        {
            Console.WriteLine("I6 getdata called");
        }
        static void Main(string[] args)
        {
            MyClass obj = new MyClass();
            ((I5)obj).getdata();                     

            Console.ReadLine();    
        }
    }
}
Debendra Dash
źródło