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


10

Poiché il principio di segregazione dell'interfaccia suggerisce che nessun client dovrebbe essere costretto a dipendere da metodi che non utilizza, 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.

E i metodi concreti? Devo separare i metodi che non tutti i client utilizzerebbero? 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 sopra, CarShop non utilizza affatto il metodo isQualityPass () in Car, se dovessi separare isQualityPass () in una nuova classe:

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

al fine di ridurre l'accoppiamento di CarShop? Perché una volta penso che isQualityPass () abbia bisogno di ulteriore dipendenza, ad esempio:

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

CarShop dipenderebbe da HttpClient anche se non utilizza mai HttpClient in realtà. Quindi la mia domanda è: secondo il principio di segregazione dell'interfaccia, dovrei separare i metodi concreti che non tutti i client utilizzerebbero, in modo che tali metodi dipendono dal client solo quando il client effettivamente utilizza, al fine di ridurre l'accoppiamento?


2
Di solito un'auto sa quando passa la "qualità"? O è forse una regola aziendale che potrebbe essere incapsulata da sola?
Laiv

2
Come suggerisce la parola interfaccia in ISP, si tratta di interfacce . Quindi se hai un metodo nella tua Carclasse di cui non vuoi che tutti gli utenti siano a conoscenza, crea (più di una) interfaccia che la Carclasse implementa e che dichiari utili solo i metodi nel contesto delle interfacce.
Timothy Truckle,

@Laiv Sono sicuro che vedremo presto veicoli che ne sanno molto di più. ;)
sandwich di modellazione unificato

1
Un'auto saprà cosa vuole il suo produttore. La Volkswagen sa di cosa sto parlando :-)
Laiv,

1
Hai menzionato un'interfaccia, ma non c'è un'interfaccia nel tuo esempio. Stiamo parlando di trasformare Car in un'interfaccia e quali metodi includere in detta interfaccia?
Neil,

Risposte:


6

Nel tuo esempio, CarShopnon dipende da isQualityPass, e non è costretto a fare un'implementazione vuota per un metodo. Non c'è nemmeno un'interfaccia coinvolta. Quindi il termine "ISP" semplicemente non corrisponde qui. E fintanto che un metodo simile isQualityPassè un metodo che si adatta bene Carall'oggetto, senza sovraccaricarlo di ulteriori responsabilità o dipendenze, va bene. Non è necessario refactificare un metodo pubblico di una classe in un altro posto solo perché esiste un client che non utilizza il metodo.

Tuttavia, rendere una classe di dominio come Cardirettamente dipendente da qualcosa di simile HttpClientprobabilmente non è una buona idea, indipendentemente dal tipo di client utilizzato o meno. Spostare la logica in una classe separata CheckCarQualityPassnon si chiama semplicemente "ISP", questo si chiama "separazione delle preoccupazioni" . La preoccupazione di un oggetto auto riutilizzabile probabilmente non dovrebbe essere quella di effettuare chiamate HTTP esterne, almeno non direttamente, ciò limita la riusabilità e inoltre la testabilità troppo.

Se isQualityPassnon fosse possibile spostarlo facilmente in un'altra classe, l'alternativa sarebbe quella di effettuare Httpchiamate attraverso un'interfaccia astratta IHttpClientche viene iniettata in fase Cardi costruzione o iniettando l'intera strategia di controllo "QualityPass" (con la richiesta Http incapsulata) Carnell'oggetto . Ma questa è IMHO solo la seconda soluzione migliore, poiché aumenta la complessità complessiva invece di ridurla.


che dire del modello di strategia per risolvere il metodo isQualityPass?
Laiv,

@Laiv: tecnicamente, questo funzionerà, certo (vedi la mia modifica), ma si tradurrà in un Caroggetto più complesso . Non sarebbe la mia prima scelta per una soluzione (almeno non nel contesto di questo esempio inventato). Tuttavia, potrebbe avere più senso nel codice "reale", non lo so.
Doc Brown,

6

Quindi la mia domanda è: secondo il principio di segregazione dell'interfaccia, dovrei separare i metodi concreti che non tutti i client utilizzerebbero, in modo che tali metodi dipendono dal client solo quando il client effettivamente utilizza, al fine di ridurre l'accoppiamento?

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

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

Ciò che viene utilizzato qui è getTax()e getCost(). Ciò su cui si insiste è tutto accessibile Car. Il problema insiste sul fatto Carche insiste sull'accesso a isQualityPass()cui non è necessario.

Questo può essere risolto. Chiedete 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 Carpotrebbe implementarla. Questo non violerebbe l'ISP perché anche se isQuality()è in Car CarShopnon insistere su di esso. Questo va bene.

Se è concreto, potrebbe isQuality()non esistere o essere stato spostato altrove. Questo va bene.

Potrebbe anche essere CarLiabilityun involucro di cemento attorno a Carcui sta delegando il lavoro. Finché CarLiabilitynon espone isQuality()quindi CarShopva bene. Ovviamente questo non fa altro che calciare la lattina lungo la strada e CarLiabilitydeve capire come seguire l'ISP Carnello stesso modo in cui CarShopdoveva fare.

In breve, isQuality()non è necessario rimuoverlo a Carcausa dell'ISP. Il bisogno implicito di necessità isQuality()deve essere rimosso CarShopperché CarShopnon ne ha bisogno, quindi non dovrebbe chiederlo.


4

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

E i metodi concreti? Devo separare i metodi che non tutti i client utilizzerebbero?

Non proprio. Ci sono diversi modi per nascondere Car.isQualityPassda CarShop.

1. Modificatori di accesso

Dal punto di vista della Legge di Demetra , potremmo considerare Care CardShopnon 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{
    ...
}

Essere consapevoli del fatto che entrambi i componenti sono in pacchetti diversi. Ora CarShopnon ha visibilità sui comportamenti Car protetti . (Mi scusi in anticipo se l'esempio sopra sembra così semplicistico).

2. Segregazione dell'interfaccia

L' ISP funziona 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 dei ruoli .

Nonostante l' Carimplementazione effettiva , nulla ci impedisce di praticare l'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 Care CarShopattraverso l' interfaccia del ruolo Fatturabile . Essere consapevoli del cambiamento sulla getPricefirma. Ho modificato intenzionalmente l'argomento. Volevo rendere evidente che CarShopè solo "legato / associato" a una delle interfacce di ruolo disponibili. Avrei potuto seguire l'implementazione effettiva ma non conosco i dettagli dell'implementazione reale e temo che l' getPrice(String carId)accesso effettivo (visibilità) sulla classe concreta. In tal caso, tutto il lavoro svolto con l'ISP diventa inutile perché è nelle mani dello sviluppatore eseguire il casting e lavorare solo con l'interfaccia fatturabile . Non importa quanto metodici siamo, la tentazione sarà sempre lì.

3. Responsabilità unica

Temo di non essere in grado di dire se la dipendenza tra Cared HttpClientè adeguata, ma sono d'accordo con @DocBrown, solleva alcuni avvertimenti che meritano una revisione del design. Né Demeter's Law né ISP renderanno il tuo progetto "migliore" a questo punto. Semplicemente maschereranno il problema, non risolverlo.

Ho suggerito a DocBrown il modello di strategia come possibile soluzione. Ho concordato con lui che il modello aggiunge complessità, ma penso anche che qualsiasi riprogettazione lo farà. È un compromesso, più disaccoppiamento vogliamo, più parti mobili abbiamo (di solito). Ad ogni modo, penso che entrambi siano d'accordo con una riprogettazione è altamente consigliabile.

Riassumendo

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.