O ile wiem, puste interfejsy są ogólnie uważane za złą praktykę - szczególnie tam, gdzie język obsługuje takie atrybuty, jak atrybuty.
Czy jednak interfejs jest uważany za „pusty”, jeśli dziedziczy po innych interfejsach?
interface I1 { ... }
interface I2 { ... } //unrelated to I1
interface I3
: I1, I2
{
// empty body
}
Wszystko, narzędzia I3
będą potrzebne do realizacji I1
i I2
, a obiekty z różnych klas, które dziedziczą I3
mogą następnie być stosowane zamiennie (patrz poniżej), więc jest to prawo zadzwonić I3
pusty ? Jeśli tak, jaki byłby lepszy sposób na architekturę?
// with I3 interface
class A : I3 { ... }
class B : I3 { ... }
class Test {
void foo() {
I3 something = new A();
something = new B();
something.SomeI1Function();
something.SomeI2Function();
}
}
// without I3 interface
class A : I1, I2 { ... }
class B : I1, I2 { ... }
class Test {
void foo() {
I1 something = new A();
something = new B();
something.SomeI1Function();
something.SomeI2Function(); // we can't do this without casting first
}
}
foo
? (Jeśli odpowiedź brzmi „tak”, to śmiało!)var : I1, I2 something = new A();
dla zmiennych lokalnych (gdybyśmy zignorowali dobre punkty podniesione w odpowiedzi Konrada)Odpowiedzi:
„Wiązanie”
I1
iI2
doI3
oferuje ładny (?) Skrót lub jakiś cukier składniowy, gdziekolwiek chcesz, aby oba były zaimplementowane.Problem z tym podejściem polega na tym, że nie można powstrzymać innych programistów przed bezpośrednim wdrożeniem
I1
i jednoczesnym wdrażaniemI2
, ale nie działa to w obie strony - chociaż: I3
jest równoważne: I1, I2
,: I1, I2
nie jest równoważne z: I3
. Nawet jeśli są funkcjonalnie identyczne, klasa obsługuje oba te elementyI1
iI2
nie zostanie wykryta jako obsługującaI3
.Oznacza to, że oferujesz dwa sposoby osiągnięcia tego samego - „ręczny” i „słodki” (z cukrem składniowym). I może to mieć zły wpływ na spójność kodu. Oferowanie 2 różnych sposobów, aby osiągnąć to samo tutaj, tam, w innym miejscu, daje już 8 możliwych kombinacji :)
OK, nie możesz. Ale może to właściwie dobry znak?
Jeśli
I1
iI2
pociągają za sobą różne obowiązki (w przeciwnym razie nie byłoby potrzeby ich rozdzielania w pierwszej kolejności), to możefoo()
błędem jest próbasomething
wykonania dwóch różnych obowiązków za jednym razem?Innymi słowy, może być tak, że
foo()
samo narusza zasadę pojedynczej odpowiedzialności, a fakt, że wymaga sprawdzenia typu rzutowania i wykonania w celu jego uruchomienia, ostrzega nas o tym czerwoną flagą.EDYTOWAĆ:
Jeśli nalegałeś, aby upewnić się, że
foo
pobiera coś, co implementujeI1
iI2
, możesz przekazać je jako osobne parametry:I mogą być tym samym przedmiotem:
Co
foo
obchodzi, czy są to te same wystąpienia, czy nie?Ale jeśli to obchodzi (np. Ponieważ nie są bezpaństwowcami) lub po prostu uważasz, że to wygląda brzydko, zamiast tego można użyć ogólnych:
Teraz ogólne ograniczenia dbają o pożądany efekt, nie zanieczyszczając jednak świata zewnętrznego niczym. Jest to idiomatyczny język C #, oparty na kontrolach czasu kompilacji, i ogólnie wydaje się bardziej odpowiedni.
Widzę
I3 : I2, I1
trochę jako próba naśladować typów związków w języku, który nie obsługuje go z pudełka (C # lub Java nie, natomiast typy związkowi istnieć w językach funkcyjnych).Próba emulacji konstrukcji, która tak naprawdę nie jest obsługiwana przez język, jest często niezręczna. Podobnie w języku C # możesz użyć metod rozszerzenia i interfejsów, jeśli chcesz emulować mixiny . Możliwy? Tak. Dobry pomysł? Nie jestem pewny.
źródło
Jeśli szczególnie ważne było, aby klasa implementowała oba interfejsy, nie widzę nic złego w tworzeniu trzeciego interfejsu, który dziedziczy po obu. Byłbym jednak ostrożny, aby nie wpaść w pułapkę nadinżynierii. Klasa może dziedziczyć po jednym lub drugim lub obu. O ile mój program nie ma wielu klas w tych trzech przypadkach, tworzenie interfejsu dla obu jest trochę, a na pewno nie, jeśli te dwa interfejsy nie mają ze sobą żadnego związku (proszę nie ma interfejsów IComparableAndSerializable).
Przypominam, że możesz również stworzyć klasę abstrakcyjną, która dziedziczy z obu interfejsów, i możesz użyć tej klasy abstrakcyjnej zamiast I3. Myślę, że błędem byłoby powiedzieć, że nie powinieneś tego robić, ale powinieneś przynajmniej mieć dobry powód.
źródło
Nie zgadzam się. Przeczytaj o interfejsach znaczników .
źródło
instanceof
zamiast polimorfizmu