Il principio di segregazione dell'interfaccia si applica ai metodi concreti?

11

Poiché il principio di separazione delle interfacce suggerisce che nessun client dovrebbe essere costretto a dipendere da metodi che non usa, quindi un client non dovrebbe implementare un metodo vuoto per i suoi metodi di interfaccia, altrimenti questo metodo di interfaccia dovrebbe essere inserito in un'altra interfaccia.

Ma per quanto riguarda i metodi concreti? Devo separare i metodi che non tutti i client dovrebbero usare? Considera la seguente classe:

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;
    }
}

al codice precedente, CarShop non usa affatto il metodo isQualityPass () in Car, dovrei separare isQualityPass () in una nuova classe:

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

per ridurre l'accoppiamento di CarShop? Perché penso una volta se isQualityPass () ha bisogno di una dipendenza extra, ad esempio:

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

CarShop dipenderebbe da HttpClient anche se non usa mai HttpClient. Quindi la mia domanda è: secondo il principio dell'interfaccia-segregazione, dovrei separare i metodi concreti che non tutti i client userebbero, in modo che quei metodi dipendano dal client solo quando il client effettivamente lo usa, al fine di ridurre l'accoppiamento?

    
posta ggrr 12.06.2017 - 09:57
fonte

3 risposte

6

Nel tuo esempio, CarShop non dipende da isQualityPass , e non è obbligato a realizzare un'implementazione vuota per un metodo. Non c'è nemmeno un'interfaccia in questione. Quindi il termine "ISP" semplicemente non coincide qui. E finché un metodo come isQualityPass è un metodo che si adatta bene all'oggetto Car , senza sovraccaricarlo di responsabilità o dipendenze aggiuntive, va bene. Non è necessario rifattorizzare un metodo pubblico di una classe in un altro posto solo perché esiste un client che non utilizza il metodo.

Tuttavia, creare una classe di dominio come Car direttamente dipendente da qualcosa come HttpClient non è probabilmente una buona idea, indipendentemente dal fatto che i client usano o meno il metodo. Spostare la logica in una classe separata CheckCarQualityPass non è semplicemente chiamata "ISP", si chiama "separazione delle preoccupazioni" . La preoccupazione di un oggetto auto riutilizzabile non dovrebbe probabilmente essere quella di effettuare chiamate HTTP esterne, almeno non direttamente, questo limita la riusabilità e inoltre la testabilità troppo.

Se isQualityPass non può essere facilmente spostato in un'altra classe, l'alternativa sarebbe rendere le chiamate Http attraverso un'interfaccia astratta IHttpClient che viene iniettata in Car in fase di costruzione o iniettando l'intero Strategia di controllo "QualityPass" (con la richiesta Http incapsulata) nell'oggetto Car . Ma questa è solo la seconda soluzione migliore, poiché aumenta la complessità complessiva invece di ridurla.

    
risposta data 12.06.2017 - 13:45
fonte
6

So my question is: according to interface-segregation principle, should I separate concrete methods that not every clients would use, so that those methods depend on client only when the client actually uses, in order to reduce coupling?

Il principio di segregazione dell'interfaccia non riguarda l'impossibilità di accedere a ciò che non è necessario. Si tratta di non insistere sull'accesso a ciò di cui non hai bisogno.

Le interfacce non sono di proprietà della classe che le implementa. Sono di proprietà degli oggetti che li usano.

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

Quello che viene utilizzato qui è getTax() e getCost() . Ciò su cui si insiste è tutto accessibile attraverso Car . Il problema è insistere su Car significa che sta insistendo sull'accesso a isQualityPass() che non è necessario.

Questo può essere risolto. Chiedi se può essere risolto concretamente. Può.

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

Nessuno di quel codice sa nemmeno se CarLiability è un'interfaccia o una classe concreta. È una buona cosa. Non vuole saperlo.

Se è un'interfaccia Car potrebbe implementarla. Ciò non violerebbe ISP perché anche se isQuality() è in Car CarShop non insiste su di esso. Questo va bene.

Se è concreto potrebbe essere che isQuality() non esista o sia stato spostato altrove. Questo va bene.

Potrebbe anche darsi che CarLiability sia un involucro concreto attorno a Car che sta delegando il lavoro ad esso. Fintanto che CarLiability non espone isQuality() allora CarShop va bene. Ovviamente, questo fa solo scalpore sulla lattina e CarLiability deve capire come seguire ISP con Car nello stesso modo in cui CarShop ha dovuto fare.

In breve, isQuality() non deve essere rimosso da Car a causa dell'ISP. La necessità implicita di isQuality() deve essere rimossa da CarShop perché CarShop non ne ha bisogno, quindi non dovrebbe chiederlo.

    
risposta data 12.06.2017 - 13:47
fonte
4

Does interface segregation principle apply to concrete methods?

But how about concrete methods? Should I separate the methods that not every client would use?

Non proprio. Esistono diversi modi per nascondere Car.isQualityPass da CarShop .

1. Modificatori di accesso

Dal punto di vista Demeter's , potremmo considerare Car e CardShop di non essere amici . Ci legittima a fare il prossimo.

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

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

Tieni presente che entrambi i componenti si trovano in pacchetti diversi. Ora CarShop non ha visibilità sui comportamenti Car protetti . (Mi scusi in anticipo se l'esempio sopra sembra così semplicistico).

2. Interfaccia segregazione

Il ISP funziona partendo dal presupposto che lavoriamo con le astrazioni, non con le classi concrete. Presumo che tu abbia già familiarità con l'implementazione dell'ISP e con le interfacce di ruolo .

Nonostante l'effettiva implementazione di Car , nulla ci impedisce di praticare 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();
  }
}

Quello che ho fatto qui. Ho ristretto l'interazione tra Car e CarShop tramite l'interfaccia di ruolo Fatturabile . Tieni presente la modifica della firma getPrice . Ho modificato intenzionalmente l'argomento. Volevo chiarire che CarShop è solo "legato / vincolato" a una delle interfacce di ruolo disponibili. Avrei potuto seguire l'effettiva implementazione ma non conosco i reali dettagli di implementazione e temo che il% effettivo di% co_de abbia accesso (visibilità) alla classe concreta. Se è così, tutto il lavoro svolto con l'ISP diventa inutile perché è nelle mani dello sviluppatore fare casting e lavorare solo con l'interfaccia Billable . Non importa quanto siamo metodici, la tentazione sarà sempre lì.

3. Responsabilità singola

Temo di non essere in grado di dire se la dipendenza tra getPrice(String carId) e Car è adeguata, ma sono d'accordo con @DocBrown, solleva alcuni avvertimenti che meritano una revisione del progetto. Né la legge di Demeter né l'ISP renderanno il tuo progetto "migliore" a questo punto. Si limiteranno a mascherare il problema, non a risolverlo.

Ho suggerito a DocBrown il Pattern di strategia come possibile soluzione. Sono d'accordo con lui sul fatto che il pattern aggiunge complessità, ma penso anche che qualsiasi riprogettazione lo farà. È un trade-off, più disaccoppiamento vogliamo, più parti mobili abbiamo (di solito). Ad ogni modo, penso che sia d'accordo con un re-design è altamente consigliabile.

Riepilogo

No, non è necessario spostare i metodi concreti verso le classi esterne per non renderli accessibili. Potrebbero esserci innumerevoli consumatori. Sposteresti tutti i metodi concreti verso le classi esterne ogni volta che un nuovo consumatore entra in gioco? Spero che tu non lo faccia.

    
risposta data 13.06.2017 - 09:41
fonte

Leggi altre domande sui tag