Le principe de ségrégation des interfaces s'applique-t-il aux méthodes concrètes?

10

Comme le principe de ségrégation d'interface suggère qu'aucun client ne devrait être forcé de dépendre de méthodes qu'il n'utilise pas, donc un client ne devrait pas implémenter une méthode vide pour ses méthodes d'interface, sinon cette méthode d'interface devrait être placée dans une autre interface.

Mais qu'en est-il des méthodes concrètes? Dois-je séparer les méthodes que tous les clients n'utiliseraient pas? Considérez la classe suivante:

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

dans le code ci-dessus, CarShop n'utilise pas du tout la méthode isQualityPass () dans Car, si je sépare isQualityPass () dans une nouvelle classe:

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

afin de réduire le couplage de CarShop? Parce que je pense qu'une fois si isQualityPass () a besoin d'une dépendance supplémentaire, par exemple:

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

CarShop dépendrait de HttpClient même s'il n'utilise jamais HttpClient en fait. Ma question est donc la suivante: selon le principe de la ségrégation d'interfaces, dois-je séparer des méthodes concrètes que tous les clients n'utiliseraient pas, afin que ces méthodes ne dépendent du client que lorsque le client l'utilise réellement, afin de réduire le couplage?

ggrr
la source
2
Est-ce qu'une voiture sait généralement quand elle passe la "qualité"? Ou c'est peut-être une règle commerciale qui pourrait être encapsulée par elle-même?
Laiv
2
Comme le mot interface dans ISP l'indique, il s'agit d' interfaces . Donc, si vous avez une méthode dans votre Carclasse que vous ne voulez pas que (tous) les utilisateurs connaissent, créez (plus d'une) interface que la Carclasse implémente et qui déclare uniquement les méthodes utiles dans le contexte des interfaces.
Timothy Truckle
@Laiv Je suis sûr que nous allons bientôt voir des véhicules qui en savent beaucoup plus que cela. ;)
sandwich de modélisation unifié
1
Une voiture saura ce que son constructeur veut qu'il sache. Volkswagen sait de quoi je parle :-)
Laiv
1
Vous mentionnez une interface, mais il n'y a pas d'interface dans votre exemple. Parlons-nous de transformer Car en interface et quelles méthodes inclure dans cette interface?
Neil

Réponses:

6

Dans votre exemple, CarShopne dépend pas isQualityPasset n'est pas obligé de faire une implémentation vide pour une méthode. Il n'y a même pas d'interface impliquée. Ainsi, le terme "FAI" ne correspond tout simplement pas ici. Et tant qu'une méthode comme isQualityPassest une méthode qui s'intègre bien dans l' Carobjet, sans le surcharger de responsabilités ou de dépendances supplémentaires, c'est très bien. Il n'est pas nécessaire de refactoriser une méthode publique d'une classe dans un autre endroit simplement parce qu'il existe un client n'utilisant pas la méthode.

Cependant, faire Cardépendre directement une classe de domaine comme quelque chose de similaire HttpClientn'est probablement pas une bonne idée non plus, quels que soient les clients qui utilisent ou non la méthode. Déplacer la logique dans une classe distincte CheckCarQualityPassn'est tout simplement pas appelé "FAI", cela s'appelle "séparation des préoccupations" . Le souci d'un objet voiture réutilisable ne devrait probablement pas être de faire des appels HTTP externes, du moins pas directement, cela limite trop la réutilisabilité et de surcroît la testabilité.

S'il isQualityPassne peut pas être facilement déplacé vers une autre classe, l'alternative serait de passer les Httpappels via une interface abstraite IHttpClientqui est injectée Carau moment de la construction, ou en injectant toute la stratégie de vérification "QualityPass" (avec la requête Http encapsulée) dans l' Carobjet . Mais ce n'est à mon humble avis que la deuxième meilleure solution, car elle augmente la complexité globale au lieu de la réduire.

Doc Brown
la source
qu'en est-il du modèle de stratégie pour résoudre la méthode isQualityPass?
Laiv
@Laiv: techniquement, cela fonctionnera, bien sûr (voir ma modification), mais il en résultera un Carobjet plus complexe . Ce ne serait pas mon premier choix pour une solution (du moins pas dans le contexte de cet exemple artificiel). Cependant, cela peut avoir plus de sens dans le "vrai" code, je ne sais pas.
Doc Brown
6

Ma question est donc la suivante: selon le principe de la ségrégation d'interfaces, dois-je séparer des méthodes concrètes que tous les clients n'utiliseraient pas, afin que ces méthodes ne dépendent du client que lorsque le client l'utilise réellement, afin de réduire le couplage?

Le principe de ségrégation d'interface ne consiste pas à interdire l'accès à ce dont vous n'avez pas besoin. Il s'agit de ne pas insister sur l'accès à ce dont vous n'avez pas besoin.

Les interfaces n'appartiennent pas à la classe qui les implémente. Ils appartiennent aux objets qui les utilisent.

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

Ce qui est utilisé ici est getTax()et getCost(). Ce sur quoi on insiste, c'est tout ce qui est accessible à travers Car. Le problème insiste sur Carsignifie qu'il insiste sur l'accès à isQualityPass()ce qui n'est pas nécessaire.

Cela peut être corrigé. Vous demandez si cela peut être corrigé concrètement. Ça peut.

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

Aucun de ce code ne sait même s'il CarLiabilitys'agit d'une interface ou d'une classe concrète. C'est une bonne chose. Il ne veut pas savoir.

Si c'est une interface qui Carpourrait l'implémenter. Cela ne violerait pas le FAI parce que même s'il isQuality()est en Car CarShopn'insiste pas dessus. C'est bon.

Si c'est concret, il se peut que isQuality()soit il n'existe pas, soit il a été transféré ailleurs. C'est bon.

Il se peut également que ce CarLiabilitysoit un emballage concret Carqui lui délègue du travail. Tant que CarLiabilityne pas exposer isQuality()alors CarShopest très bien. Bien sûr, cela ne fait que lancer la boîte et CarLiabilitydoit trouver comment suivre les FAI de Carla même manière CarShop.

En bref, isQuality()n'a pas besoin d'être supprimé à Carcause du FAI. Le besoin implicite de isQuality()besoins doit être supprimé CarShopcar il CarShopn'en a pas besoin, il ne devrait donc pas le demander.

candied_orange
la source
4

Le principe de ségrégation des interfaces s'applique-t-il aux méthodes concrètes?

Mais qu'en est-il des méthodes concrètes? Dois-je séparer les méthodes que tous les clients n'utiliseraient pas?

Pas vraiment. Il existe différentes façons de cacher à Car.isQualityPasspartir CarShop.

1. Modificateurs d'accès

Du point de vue de la loi de Demeter , nous pourrions considérer Caret CardShopne pas être amis . Il nous légitime de faire le prochain.

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

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

Soyez conscient que les deux composants sont dans des packages différents. N'a désormais CarShopaucune visibilité sur Car les comportements protégés . (Excusez-moi à l'avance si l'exemple ci-dessus semble si simpliste).

2. Séparation des interfaces

Le FAI part du principe que nous travaillons avec des abstractions, pas avec des classes concrètes. Je suppose que vous connaissez déjà la mise en œuvre du FAI et les interfaces de rôle .

Malgré la Carmise en œuvre effective , rien ne nous empêche de pratiquer les FAI.

//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();
  }
}

Ce que j'ai fait ici. J'ai réduit l'interaction entre Caret CarShopvia l' interface de rôle Billable . Soyez conscient du changement sur la getPricesignature. J'ai modifié intentionnellement l'argument. Je voulais rendre évident qu'il CarShopn'est "lié / lié" qu'à l'une des interfaces de rôle disponibles. J'aurais pu suivre l'implémentation réelle mais je ne connais pas les détails réels de l'implémentation et j'ai peur que le réel getPrice(String carId)ait accès (visibilité) à la classe concrète. Si c'est le cas, tout le travail effectué avec le FAI devient inutile car il est entre les mains du développeur de faire du casting et de travailler uniquement avec l'interface Billable . Quelle que soit notre méthode, la tentation sera toujours là.

3. Responsabilité unique

Je crains de ne pas être en mesure de dire si la dépendance entre Caret HttpClientest adéquate, mais je suis d'accord avec @DocBrown, cela soulève quelques avertissements qui méritent une révision de conception. Ni la loi de Demeter ni le FAI ne rendront votre conception "meilleure" à ce stade. Ils vont simplement masquer le problème, pas le résoudre.

J'ai suggéré DocBrown le modèle de stratégie comme une solution possible. Je suis d'accord avec lui que le motif ajoute de la complexité, mais je pense également que toute refonte le sera. C'est un compromis, plus nous voulons de découplage, plus nous avons de pièces mobiles (généralement). Quoi qu'il en soit, je pense que les deux sont d'accord avec une refonte est fortement conseillé.

Résumer

Non, vous n'avez pas besoin de déplacer des méthodes concrètes vers des classes externes car ne les rendez pas accessibles. Il pourrait y avoir d'innombrables consommateurs. Pourriez-vous déplacer toutes les méthodes concrètes vers des classes externes chaque fois qu'un nouveau consommateur entre en jeu? J'espère que non.

Laiv
la source