Podczas czytania artykułów o ISP wydaje się, że istnieją dwie sprzeczne definicje ISP:
Zgodnie z pierwszą definicją (patrz 1 , 2 , 3 ), ISP stwierdza, że klasy implementujące interfejs nie powinny być zmuszane do implementacji funkcji, których nie potrzebują. Tak więc interfejs tłuszczuIFat
interface IFat
{
void A();
void B();
void C();
void D();
}
class MyClass: IFat
{ ... }
powinny być podzielone na mniejsze interfejsy ISmall_1
iISmall_2
interface ISmall_1
{
void A();
void B();
}
interface ISmall_2
{
void C();
void D();
}
class MyClass:ISmall_2
{ ... }
ponieważ w ten sposób mój MyClass
jest w stanie zaimplementować tylko te metody, których potrzebuje ( D()
i C()
), nie będąc zmuszonym do zapewnienia również implementacji fikcyjnych A()
, B()
oraz C()
:
Ale zgodnie z drugą definicją (patrz 1 , 2 , odpowiedź Nazara Merzy ), ISP stwierdza, że MyClient
wywołanie metod MyService
nie powinno być świadome metod MyService
, których nie potrzebuje. Innymi słowy, jeśli MyClient
potrzebuje tylko funkcji C()
i D()
, a następnie zamiast
class MyService
{
public void A();
public void B();
public void C();
public void D();
}
/*client code*/
MyService service = ...;
service.C();
service.D();
powinniśmy segregować MyService's
metody na interfejsy specyficzne dla klienta :
public interface ISmall_1
{
void A();
void B();
}
public interface ISmall_2
{
void C();
void D();
}
class MyService:ISmall_1, ISmall_2
{ ... }
/*client code*/
ISmall_2 service = ...;
service.C();
service.D();
Tak więc, zgodnie z pierwszą definicją, celem ISP jest „ ułatwienie życia klasom implementującym interfejs IFat ”, podczas gdy w drugim przypadku celem ISP jest „ ułatwienie życia klientom wywołującym metody MyService ”.
Która z dwóch różnych definicji dostawcy usług internetowych jest rzeczywiście poprawna?
@MARJAN VENEMA
1.
Kiedy więc zamierzasz podzielić IFat na mniejszy interfejs, jakie metody ostatecznie decydują o wyborze ISmallinterface na podstawie spójności elementów.
Chociaż sensowne jest umieszczenie spójnych metod w tym samym interfejsie, pomyślałem, że przy wzorcu ISP potrzeby klienta mają pierwszeństwo przed „spójnością” interfejsu. Innymi słowy, pomyślałem, że z ISP powinniśmy skupić w tym samym interfejsie metody potrzebne konkretnym klientom, nawet jeśli oznacza to pominięcie tego interfejsu tych metod, które ze względu na spójność powinny być również umieszczone w tym samym interfejsie?
Tak więc, jeśli było wielu klientów, którzy będą musieli tylko zadzwonić CutGreens
, ale nie również GrillMeat
, to aby zastosować się do wzorca ISP, powinniśmy tylko umieścić w CutGreens
środku ICook
, ale nie również GrillMeat
, mimo że te dwie metody są bardzo spójne ?!
2)
Myślę, że twoje zamieszanie wynika z ukrytego założenia z pierwszej definicji: że klasy wdrażające przestrzegają już zasady pojedynczej odpowiedzialności.
Przez „wdrażanie klas nie przestrzegających SRP” masz na myśli te klasy, które implementują IFat
lub klasy, które implementują ISmall_1
/ ISmall_2
? Zakładam, że masz na myśli klasy, które implementują IFat
? Jeśli tak, to dlaczego zakładasz, że jeszcze nie przestrzegają SRP?
dzięki
źródło
Odpowiedzi:
Obydwa są prawidłowe
Sposób, w jaki go czytam, ma na celu utrzymanie małych interfejsów i koncentracji interfejsów: wszyscy członkowie interfejsu powinni mieć bardzo wysoką spójność. Obie definicje mają na celu uniknięcie interfejsów typu „jack-of-all-trade-master-of-none”.
Segregacja interfejsu i SRP (zasada pojedynczej odpowiedzialności) mają ten sam cel: zapewnienie małych, wysoce spójnych komponentów oprogramowania. Uzupełniają się. Segregacja interfejsów zapewnia, że interfejsy są małe, skoncentrowane i bardzo spójne. Przestrzeganie zasady pojedynczej odpowiedzialności gwarantuje, że zajęcia są małe, skoncentrowane i bardzo spójne.
Pierwsza wymieniona definicja dotyczy implementatorów, druga klientów. Co, w przeciwieństwie do @ user61852, uważam za użytkowników / wywołujących interfejs, a nie implementatorów.
Myślę, że twoje zamieszanie wynika z ukrytego założenia z pierwszej definicji: że klasy wdrażające przestrzegają już zasady pojedynczej odpowiedzialności.
Dla mnie druga definicja, w której klientami są osoby wywołujące interfejs, jest lepszym sposobem na osiągnięcie zamierzonego celu.
Segregowanie
W swoim pytaniu stwierdzasz:
Ale to wywraca świat do góry nogami.
Kiedy więc zamierzasz podzielić się
IFat
na mniejszy interfejs, które metody ostatecznieISmall
decydują o tym, który interfejs powinien zostać wybrany na podstawie spójności elementów.Rozważ ten interfejs:
Jakie metody byś zastosował
ICook
i dlaczego? CzyCleanSink
połączyłbyś toGrillMeat
tylko dlatego, że akurat masz klasę, która to robi i kilka innych rzeczy, ale nic podobnego do żadnej z innych metod? Czy podzieliłbyś go na dwa bardziej spójne interfejsy, takie jak:Uwaga dotycząca deklaracji interfejsu
Definicja interfejsu powinna najlepiej znajdować się w oddzielnej jednostce, ale jeśli absolutnie musi żyć z osobą wywołującą lub implementującą, tak naprawdę powinna być z osobą wywołującą. W przeciwnym razie program wywołujący uzyskuje bezpośrednią zależność od implementatora, co całkowicie eliminuje przeznaczenie interfejsów. Zobacz także: Deklarowanie interfejsu w tym samym pliku co klasa podstawowa, czy to dobra praktyka? o programistach i dlaczego powinniśmy umieszczać interfejsy z klasami, które ich używają, a nie tymi, które je implementują? na StackOverflow.
źródło
ICook
zamiast typuSomeCookImplementor
, jak nakazuje DIP, to nie robi tego nie musisz na tym polegaćSomeCookImplementor
.Mylisz słowo „klient” użyte w dokumentach Gang of Four z „klientem”, jak w przypadku konsumenta usługi.
„Klient”, zgodnie z zamiarem definicji Gang of Four, to klasa implementująca interfejs. Jeśli klasa A implementuje interfejs B, oznacza to, że A jest klientem B. W przeciwnym razie wyrażenie „klienci nie powinni być zmuszani do implementowania interfejsów, których nie używają” nie miałoby sensu, ponieważ „klienci” (jak w przypadku konsumentów) nie niczego nie implementować. Fraza ma sens tylko wtedy, gdy widzisz „klient” jako „implementator”.
Jeśli „klient” oznaczał klasę, która „zużywa” (wywołuje) metody innej klasy, która implementuje duży interfejs, wówczas wywołanie dwóch metod, na których Ci zależy, i zignorowanie pozostałych, wystarczyłoby, abyś odłączył się od reszty metody, których nie używasz.
Istotą tej zasady jest unikanie, aby „klient” (klasa implementująca interfejs) musiał implementować fikcyjne metody w celu zachowania zgodności z całym interfejsem, gdy dba on tylko o zestaw powiązanych metod.
Ma również na celu jak najmniejszą ilość sprzężenia, aby zmiany dokonane w jednym miejscu powodowały mniejszy wpływ. Dzięki segregacji interfejsów zmniejszasz sprzężenie.
Problemy te pojawiają się, gdy interfejs robi zbyt wiele i ma metody, które powinny być podzielone na kilka interfejsów zamiast tylko jednego.
Oba przykłady kodu są w porządku . Tyle tylko, że w drugim zakładasz, że „klient” oznacza „klasę, która wykorzystuje / wywołuje usługi / metody oferowane przez inną klasę”.
Nie znalazłem żadnych sprzeczności w pojęciach wyjaśnionych w trzech podanych linkach.
Po prostu wyjaśnij, że „klient” jest implementatorem w rozmowie SOLID.
źródło
ISP polega przede wszystkim na odizolowaniu klienta od wiedzy na temat usługi, niż powinna wiedzieć (na przykład ochrony przed niepowiązanymi zmianami). Twoja druga definicja jest poprawna. Według mojego czytania tylko jeden z tych trzech artykułów sugeruje coś innego ( pierwszy ) i jest to po prostu błędne. (Edycja: Nie, nie jest źle, po prostu wprowadza w błąd.)
Pierwsza definicja jest znacznie ściślej związana z LSP.
źródło