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?
Car
classe que vous ne voulez pas que (tous) les utilisateurs connaissent, créez (plus d'une) interface que laCar
classe implémente et qui déclare uniquement les méthodes utiles dans le contexte des interfaces.Réponses:
Dans votre exemple,
CarShop
ne dépend pasisQualityPass
et 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 commeisQualityPass
est une méthode qui s'intègre bien dans l'Car
objet, 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
Car
dépendre directement une classe de domaine comme quelque chose de similaireHttpClient
n'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 distincteCheckCarQualityPass
n'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
isQualityPass
ne peut pas être facilement déplacé vers une autre classe, l'alternative serait de passer lesHttp
appels via une interface abstraiteIHttpClient
qui est injectéeCar
au moment de la construction, ou en injectant toute la stratégie de vérification "QualityPass" (avec la requête Http encapsulée) dans l'Car
objet . 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.la source
Car
objet 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.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.
Ce qui est utilisé ici est
getTax()
etgetCost()
. Ce sur quoi on insiste, c'est tout ce qui est accessible à traversCar
. Le problème insiste surCar
signifie 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.
Aucun de ce code ne sait même s'il
CarLiability
s'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
Car
pourrait l'implémenter. Cela ne violerait pas le FAI parce que même s'ilisQuality()
est enCar
CarShop
n'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
CarLiability
soit un emballage concretCar
qui lui délègue du travail. Tant queCarLiability
ne pas exposerisQuality()
alorsCarShop
est très bien. Bien sûr, cela ne fait que lancer la boîte etCarLiability
doit trouver comment suivre les FAI deCar
la même manièreCarShop
.En bref,
isQuality()
n'a pas besoin d'être supprimé àCar
cause du FAI. Le besoin implicite deisQuality()
besoins doit être suppriméCarShop
car ilCarShop
n'en a pas besoin, il ne devrait donc pas le demander.la source
Pas vraiment. Il existe différentes façons de cacher à
Car.isQualityPass
partirCarShop
.1. Modificateurs d'accès
Du point de vue de la loi de Demeter , nous pourrions considérer
Car
etCardShop
ne pas être amis . Il nous légitime de faire le prochain.Soyez conscient que les deux composants sont dans des packages différents. N'a désormais
CarShop
aucune visibilité surCar
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
Car
mise en œuvre effective , rien ne nous empêche de pratiquer les FAI.Ce que j'ai fait ici. J'ai réduit l'interaction entre
Car
etCarShop
via l' interface de rôle Billable . Soyez conscient du changement sur lagetPrice
signature. J'ai modifié intentionnellement l'argument. Je voulais rendre évident qu'ilCarShop
n'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éelgetPrice(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
Car
etHttpClient
est 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.
la source