Czy zasada segregacji interfejsów ma zastosowanie do konkretnych metod?

10

Ponieważ zasada segregacji interfejsów sugeruje, że żaden klient nie powinien być zmuszany do polegania na metodach, których nie używa, więc klient nie powinien implementować pustej metody dla swoich metod interfejsu, w przeciwnym razie ta metoda interfejsu powinna zostać umieszczona w innym interfejsie.

A co z konkretnymi metodami? Czy powinienem oddzielić metody, których nie używałby każdy klient? Rozważ następującą klasę:

public class Car{
    ....

    public boolean isQualityPass(){
        ...
    }

    public int getTax(){
        ...
    }

    public int getCost(){
        ...
    }
}

public class CarShop{
    ...
    public int getCarPrice(int carId){
        Car car=carList[carId];
        int price=car.getTax() + car.getCost()... (some formula);
        return price;
    }
}

w powyższym kodzie CarShop w ogóle nie używa metody isQualityPass () w Car, czy powinienem oddzielić isQualityPass () do nowej klasy:

public class CheckCarQualityPass{
    public boolean isQualityPass(Car car){
    }
}

w celu zmniejszenia sprzężenia CarShop? Ponieważ raz myślę, że isQualityPass () potrzebuje dodatkowej zależności, np .:

public boolean isQualityPass(){
    HttpClient client=...
}

CarShop zależałby od HttpClient, nawet tak naprawdę nigdy nie używa HttpClient. Więc moje pytanie brzmi: zgodnie z zasadą segregacji interfejsów, czy powinienem oddzielić konkretne metody, których nie używałby każdy klient, aby metody te zależały od klienta tylko wtedy, gdy klient faktycznie korzysta, aby zmniejszyć sprzężenie?

grgrr
źródło
2
Czy zwykle samochód wie, kiedy przechodzi „jakość”? A może jest to reguła biznesowa, która może być enkapsulowana samodzielnie?
Laiv
2
Jak sugeruje interfejs słów w ISP, chodzi o interfejsy . Jeśli więc masz w swojej Carklasie metodę , o której nie chcą (wszyscy) użytkownicy wiedzieć, to stwórz (więcej niż jeden) interfejs, który Carimplementuje klasa, która deklaruje tylko metody przydatne w kontekście interfejsów.
Timothy Truckle,
@Laiv Jestem pewien, że wkrótce zobaczymy pojazdy, które wiedzą znacznie więcej. ;)
ujednolicona kanapka modelująca
1
Samochód będzie wiedział, co chce od producenta. Volkswagen wie, o czym mówię :-)
Laiv
1
Wspominasz o interfejsie, ale w twoim przykładzie nie ma interfejsu. Czy mówimy o przekształceniu Car w interfejs i jakie metody należy uwzględnić we wspomnianym interfejsie?
Neil

Odpowiedzi:

6

W twoim przykładzie CarShopnie zależy isQualityPassi nie jest zmuszone do wykonania pustej implementacji dla metody. Nie ma nawet interfejsu. Zatem termin „ISP” po prostu nie pasuje tutaj. I dopóki taka metoda isQualityPassjest metodą, która dobrze wpasowuje się w Carobiekt, bez obciążania go dodatkowymi obowiązkami lub zależnościami, nie ma problemu. Nie ma potrzeby refaktoryzacji publicznej metody klasy w inne miejsce tylko dlatego, że istnieje jeden klient, który nie korzysta z tej metody.

Jednak uzależnienie klasy domeny Carbezpośrednio od czegoś podobnego HttpClientprawdopodobnie nie jest dobrym pomysłem, niezależnie od tego, którzy klienci używają tej metody, czy nie. Przeniesienie logiki do oddzielnej klasy CheckCarQualityPasspo prostu nie nazywa się „ISP”, to się nazywa „rozdzielenie obaw” . Problemem samochodu wielokrotnego użytku nie powinno być prawdopodobnie wykonywanie jakichkolwiek zewnętrznych połączeń HTTP, przynajmniej nie bezpośrednio, co ogranicza możliwość ponownego użycia, a ponadto zbyt wiele możliwości testowania.

Jeśli isQualityPassnie można go łatwo przenieść do innej klasy, alternatywą byłoby wykonanie Httpwywołań za pomocą abstrakcyjnego interfejsu, IHttpClientktóry jest wstrzykiwany w Carczasie budowy, lub poprzez wstrzyknięcie całej strategii sprawdzania „QualityPass” (z enkapsulacją żądania HTTP) do Carobiektu . Ale to jest IMHO tylko drugie najlepsze rozwiązanie, ponieważ zwiększa ogólną złożoność zamiast ją zmniejszać.

Doktor Brown
źródło
co ze wzorem strategii do rozwiązania metody isQualityPass?
Laiv
@Laiv: technicznie, to zadziała, jasne (zobacz moją edycję), ale spowoduje to bardziej złożony Carobiekt. To nie byłby mój pierwszy wybór rozwiązania (przynajmniej nie w kontekście tego wymyślonego przykładu). Może to jednak mieć więcej sensu w „prawdziwym” kodzie, nie wiem.
Doc Brown
6

Więc moje pytanie brzmi: zgodnie z zasadą segregacji interfejsów, czy powinienem oddzielić konkretne metody, których nie używałby każdy klient, aby metody te zależały od klienta tylko wtedy, gdy klient faktycznie korzysta, aby zmniejszyć sprzężenie?

Zasada segregacji interfejsu nie polega na uniemożliwieniu dostępu do tego, czego nie potrzebujesz. Chodzi o to, aby nie nalegać na dostęp do tego, czego nie potrzebujesz.

Interfejsy nie są własnością klasy, która je implementuje. Są własnością obiektów, które ich używają.

public class CarShop{
    ...
    public int getCarPrice(int carId){
        Car car=carList[carId];
        int price=car.getTax() + car.getCost()... (some formula);
        return price;
    }
}

Zastosowano tutaj getTax()i getCost(). Nalegane jest wszystko, przez co można uzyskać dostęp Car. Problem polega na naleganiu Caroznacza, że ​​nalega na dostęp, do isQualityPass()którego nie jest potrzebny.

Można to naprawić. Pytasz, czy można to naprawić konkretnie. To może.

public class CarShop{
    ...
    public int getCarPrice(int carId){
        CarLiability carLiability=carLiabilityList[carId];
        int price=carLiability.getTax() + carLiability.getCost()... (some formula);
        return price;
    }
}

Żaden z tych kodów nie wie nawet, czy CarLiabilityjest interfejsem czy konkretną klasą. To dobra rzecz. Nie chce wiedzieć.

Jeśli jest to interfejs, Carmoże go zaimplementować. Nie naruszyłoby to usługodawcy internetowego, ponieważ nawet jeśli isQuality()jest na Car CarShopto nie nalega. Jest okej.

Jeśli jest konkretny, być może isQuality()nie istnieje lub został przeniesiony do innego miejsca. Jest okej.

Być może CarLiabilityjest to konkretne opakowanie, Carktóre deleguje mu pracę. Tak długo, jak CarLiabilitynie narazić isQuality()wtedy CarShopjest w porządku. Oczywiście, to po prostu kopie puszkę w dół drogi i CarLiabilitymusi dowiedzieć się, jak podążać za ISP w Cartaki sam sposób CarShop.

Krótko mówiąc, isQuality()nie trzeba go usuwać z Carpowodu usługodawcy internetowego. Implikowana potrzeba isQuality()musi zostać usunięta, CarShopponieważ CarShopnie jest potrzebna, więc nie powinna o to prosić.

candied_orange
źródło
4

Czy zasada segregacji interfejsów ma zastosowanie do konkretnych metod?

A co z konkretnymi metodami? Czy powinienem oddzielić metody, których nie używałby każdy klient?

Nie całkiem. Istnieją różne sposoby, aby ukryć Car.isQualityPassz CarShop.

1. Modyfikatory dostępu

Z punktu widzenia prawa Demetera moglibyśmy uważać się za przyjaciółCar i CardShopnie być nimi . Usprawnia to nas do zrobienia następnego.

package com.my.package.domain.model
public class Car{
    ...
    protected boolean isQualityPass(){...}
}

package com.my.package.domain.services
public class CarShop{
    ...
}

Należy pamiętać, że oba składniki znajdują się w różnych pakietach. Teraz CarShopnie ma widoczności nad Car chronionymi zachowaniami. (Przepraszam z góry, jeśli powyższy przykład wygląda na tak uproszczony).

2. Segregacja interfejsu

ISP współpracuje z założenia, że możemy pracować z abstrakcji, a nie z konkretnych klas. Zakładam, że znasz już implementację ISP i interfejsy ról .

Mimo faktycznej Carimplementacji nic nie powstrzymuje nas przed praktyką ISP.

//role interfaces 
public interface Billable{
   public int getCosts();
   public int getTaxs();
}

//role interfaces
public interface QualityAssurance{
   public boolean isQualityPass();
}

public class Car implements Billable, QualityAssurance{
   ...
}

public class CarShop {
  ...
  public int getPrice(Billable billable){
     return billable.getCosts() * billable.getTaxs();
  }
}

Co ja tu zrobiłem. Zawęziłem zakres interakcji między Cari CarShoppoprzez interfejs roli Billable . Pamiętaj o zmianie getPricepodpisu. Celowo zmodyfikowałem argument. Chciałem wyjaśnić, że CarShopjest on „związany / związany” tylko z jednym z dostępnych interfejsów ról . Mógłbym śledzić faktyczną implementację, ale nie znam prawdziwych szczegółów implementacji i obawiam się, że faktyczna getPrice(String carId)ma dostęp (widoczność) do konkretnej klasy. Jeśli tak, cała praca wykonana z ISP staje się bezużyteczna, ponieważ to w rękach dewelopera jest casting i praca tylko z interfejsem Billable . Bez względu na to, jak jesteśmy metodyczni, pokusa będzie zawsze obecna.

3. Jedna odpowiedzialność

Obawiam się, że nie jestem w stanie stwierdzić, czy zależność między Cari HttpClientjest wystarczająca, ale zgadzam się z @DocBrown, budzi to pewne ostrzeżenia, które warte są przeglądu projektu. Ani prawo Demeter, ani dostawca usług internetowych nie uczynią twojego projektu „lepszym” w tym momencie. Po prostu zamaskują problem, a nie go naprawią.

Jako możliwe rozwiązanie zasugerowałem DocBrown wzorzec strategii . Zgodziłem się z nim, że wzór dodaje złożoności, ale myślę również, że każde ponowne zaprojektowanie będzie. Jest to kompromis, im więcej chcemy oddzielić, tym więcej mamy ruchomych części (zwykle). W każdym razie uważam, że oboje zgadzają się z przeprojektowaniem jest wysoce wskazane.

Podsumowując

Nie, nie musisz przenosić konkretnych metod do klas zewnętrznych, ponieważ nie udostępniaj ich. Mogą istnieć niezliczeni konsumenci. Czy przenosiłbyś wszystkie konkretne metody do klas zewnętrznych za każdym razem, gdy pojawiał się nowy konsument ? Mam nadzieję, że nie.

Laiv
źródło