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?
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?
Odpowiedzi:
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>
i IComparable
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ł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.
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 publicznymi
silne typowanie : aby zilustrować historię supercat na przykładzie, byłby to mój preferowany przykładowy kod, implementacja ICloneable
jawnie pozwala Clone()
na wyraźne wpisywanie, gdy wywołujesz go bezpośrednio jako element MyObject
członkowski instancji:
public class MyObject : ICloneable
{
public MyObject Clone()
{
// my cloning logic;
}
object ICloneable.Clone()
{
return this.Clone();
}
}
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 protected
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 widoczna Clone
metoda zwraca swój własny typ.
Podobnie, IAutomobileFactory
może mieć Manufacture
metodę, która zwraca an Automobile
, ale a FordExplorerFactory
, która implementuje IAutomobileFactory
, może mieć Manufacture
metodę zwracającą a FordExplorer
(która pochodzi od Automobile
). Kod, który wie, że FordExplorerFactory
może użyć FordExplorer
właściwości specyficznych dla obiektu zwróconego przez a FordExplorerFactory
bez 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
.
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
}
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.
Dispose
to te, które nigdy nie będą wymagały czyszczenia); lepszym przykładem byłoby coś w rodzaju implementacji niezmiennej kolekcji IList<T>.Add
.
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.
#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 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 Add
implementacji 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();
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
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();
}
}
}