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?
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()
{
}
}
Warto ukryć niepreferowanego członka. Na przykład, jeśli zaimplementujesz oba
IComparable<T>
iIComparable
zazwyczaj lepiej jest ukryćIComparable
przeciąż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ładIConvertible
, 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ń:
Nie zapakuje int, gdy przekażesz je do niego.
źródło
string
istniało przed lekami generycznymi i ta praktyka była w modzie. Kiedy pojawił się .net 2, nie chcieli złamać publicznego interfejsu,string
więc zostawili go tak, jak był, z zabezpieczeniem na miejscu.Kilka dodatkowych powodów, aby jawnie zaimplementować interfejs:
kompatybilność wsteczna : w przypadku
ICloneable
zmiany interfejsu, implementujące elementy klasy metod nie muszą zmieniać ich sygnatur metod.czystszy kod : wystąpi błąd kompilatora, jeśli
Clone
metoda zostanie usunięta z ICloneable, jednak jeśli zaimplementujesz metodę niejawnie, możesz skończyć z nieużywanymi `` osieroconymi '' metodami publicznymisilne typowanie : aby zilustrować historię supercat na przykładzie, byłby to mój preferowany przykładowy kod, implementacja
ICloneable
jawnie pozwalaClone()
na wyraźne wpisywanie, gdy wywołujesz go bezpośrednio jako elementMyObject
członkowski instancji:źródło
interface ICloneable<out T> { T Clone(); T self {get;} }
. Zauważ, że celowo nie maICloneable<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 zprotected
metodą klonowania i zapieczętowane klasy, które z nich pochodzą i ujawniają klonowanie publiczne.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 widocznaClone
metoda zwraca swój własny typ.Podobnie,
IAutomobileFactory
może miećManufacture
metodę, która zwraca anAutomobile
, ale aFordExplorerFactory
, która implementujeIAutomobileFactory
, może miećManufacture
metodę zwracającą aFordExplorer
(która pochodzi odAutomobile
). Kod, który wie, żeFordExplorerFactory
może użyćFordExplorer
właściwości specyficznych dla obiektu zwróconego przez aFordExplorerFactory
bez konieczności typowania, podczas gdy kod, który tylko wiedział, że ma jakiś typIAutomobileFactory
, po prostu poradziłby sobie z jego powrotem jakoAutomobile
.źródło
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):
źródło
public class Animal : Cat, Dog
Może zachować czystość interfejsu publicznego, aby jawnie zaimplementować interfejs, tj. Twoja
File
klasa może zaimplementowaćIDisposable
jawnie 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.
źródło
Dispose
to te, które nigdy nie będą wymagały czyszczenia); lepszym przykładem byłoby coś w rodzaju implementacji niezmiennej kolekcjiIList<T>.Add
.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.
źródło
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.
źródło
#region
, z odpowiednim ciągiem tytułu. I komentarz do metody.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 samymICollection<T>
( w celu ułatwieniaImmutableList<T>
stosowania ze starszym kodem), alevoid ICollection<T>.Add(T item)
nie ma sensu dla aImmutableList<T>
: ponieważ dodanie elementu do niezmiennej listy nie może zmieniać istniejącej listy,ImmutableList<T>
również wywodzi się zIImmutableList<T>
któregoIImmutableList<T> Add(T item)
można użyć do niezmienne listy.Tak więc w przypadku
Add
implementacjiImmutableList<T>
ostatecznie wygląda to następująco:źródło
W przypadku jawnie zdefiniowanych interfejsów wszystkie metody są automatycznie prywatne, nie można nadać im publicznego modyfikatora dostępu. Przypuszczać:
źródło
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.
źródło