Wszystko, co implementuje I3, będzie musiało zaimplementować I1 i I2, a obiekty z różnych klas dziedziczących I3 mogą być następnie używane zamiennie (patrz poniżej), więc czy słusznie jest nazywać I3 pustym? Jeśli tak, jaki byłby lepszy sposób na architekturę?
„Wiązanie” I1
i I2
do I3
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żaniem I2
, 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 elementy I1
i I2
nie zostanie wykryta jako obsługująca I3
.
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 :)
void foo() {
I1 something = new A();
something = new B();
something.SomeI1Function();
something.SomeI2Function(); // we can't do this without casting first
}
OK, nie możesz. Ale może to właściwie dobry znak?
Jeśli I1
i I2
pociągają za sobą różne obowiązki (w przeciwnym razie nie byłoby potrzeby ich rozdzielania w pierwszej kolejności), to może foo()
błędem jest próba something
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 implementuje I1
i I2
, możesz przekazać je jako osobne parametry:
void foo(I1 i1, I2 i2)
I mogą być tym samym przedmiotem:
foo(something, something);
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:
void Foo<T>(T something) where T: I1, I2
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.