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