Lors de la lecture d'articles sur les FAI, il semble y avoir deux définitions contradictoires des FAI:
Selon la première définition (voir 1 , 2 , 3 ), le FAI déclare que les classes implémentant l'interface ne devraient pas être forcées d'implémenter des fonctionnalités dont elles n'ont pas besoin. Ainsi, la grosse interfaceIFat
interface IFat
{
void A();
void B();
void C();
void D();
}
class MyClass: IFat
{ ... }
devrait être divisé en interfaces plus petites ISmall_1
etISmall_2
interface ISmall_1
{
void A();
void B();
}
interface ISmall_2
{
void C();
void D();
}
class MyClass:ISmall_2
{ ... }
puisque de cette façon mon MyClass
est en mesure de mettre en œuvre que les méthodes dont il a besoin ( D()
et C()
), sans être obligé de fournir également des implémentations fictives pour A()
, B()
et C()
:
Mais selon la deuxième définition (voir 1 , 2 , réponse de Nazar Merza ), le FAI déclare que l' MyClient
appel de méthodes sur MyService
ne devrait pas être conscient des méthodes surMyService
lesquelles il n'a pas besoin. En d'autres termes, si MyClient
seulement besoin de la fonctionnalité de C()
et D()
, alors au lieu de
class MyService
{
public void A();
public void B();
public void C();
public void D();
}
/*client code*/
MyService service = ...;
service.C();
service.D();
nous devons séparer MyService's
méthodes en interfaces spécifiques au client :
public interface ISmall_1
{
void A();
void B();
}
public interface ISmall_2
{
void C();
void D();
}
class MyService:ISmall_1, ISmall_2
{ ... }
/*client code*/
ISmall_2 service = ...;
service.C();
service.D();
Ainsi, avec la première définition, le but du FAI est de " rendre la vie des classes implémentant l'interface IFat plus facile ", tandis qu'avec la seconde, le but du FAI est de " rendre la vie des clients appelant les méthodes de MyService plus facile ".
Laquelle des deux définitions différentes du FAI est réellement correcte?
@MARJAN VENEMA
1.
Ainsi, lorsque vous allez diviser IFat en une interface plus petite, quelles méthodes finissent dans quelle ISmallinterface doit être décidée en fonction de la cohésion des membres.
Bien qu'il soit logique de mettre des méthodes cohésives dans la même interface, je pensais qu'avec le modèle ISP, les besoins du client priment sur la "cohésion" d'une interface. En d'autres termes, je pensais qu'avec le FAI, nous devrions regrouper dans la même interface les méthodes nécessaires à des clients particuliers, même si cela signifie exclure de cette interface les méthodes qui devraient, par souci de cohésion, également être placées dans cette même interface?
Ainsi, s'il y avait beaucoup de clients qui ne jamais besoin d'appeler CutGreens
, mais pas aussi GrillMeat
, puis à respecter le modèle FAI nous ne devrions mettre à l' CutGreens
intérieur ICook
, mais pas aussi GrillMeat
, même si les deux méthodes sont très cohésif ?!
2.
Je pense que votre confusion provient d'une hypothèse cachée dans la première définition: que les classes d'implémentation suivent déjà le principe de responsabilité unique.
Par «implémentation de classes ne suivant pas SRP», faites-vous référence aux classes qui implémentent IFat
ou aux classes qui implémentent ISmall_1
/ ISmall_2
? Je suppose que vous faites référence aux classes qui implémentent IFat
? Si oui, pourquoi supposez-vous qu'ils ne suivent pas déjà le SRP?
Merci
la source
Réponses:
Les deux sont corrects
D'après ma lecture, le but de l'ISP (Interface Segregation Principle) est de garder les interfaces petites et concentrées: tous les membres de l'interface devraient avoir une cohésion très élevée. Les deux définitions visent à éviter les interfaces "jack-of-all-trades-master-of-none".
La ségrégation des interfaces et le principe de responsabilité unique (SRP) ont le même objectif: garantir de petits composants logiciels hautement cohésifs. Ils se complètent. La ségrégation des interfaces garantit que les interfaces sont petites, ciblées et hautement cohésives. Suivre le principe de la responsabilité unique garantit que les classes sont petites, ciblées et très cohésives.
La première définition que vous mentionnez se concentre sur les implémenteurs, la seconde sur les clients. Ce qui, contrairement à @ user61852, je considère être les utilisateurs / appelants de l'interface, pas les implémenteurs.
Je pense que votre confusion provient d'une hypothèse cachée dans la première définition: que les classes d'implémentation suivent déjà le principe de responsabilité unique.
Pour moi, la deuxième définition, avec les clients comme appelants de l'interface, est un meilleur moyen d'atteindre l'objectif visé.
Séparation
Dans votre question, vous déclarez:
Mais cela bouleverse le monde.
Ainsi, lorsque vous allez vous diviser
IFat
en une interface plus petite, quelles méthodes se retrouvent dans quelleISmall
interface doit être décidée en fonction de la cohésion des membres.Considérez cette interface:
Quelles méthodes mettriez-vous en place
ICook
et pourquoi? Souhaitez-vous mettre enCleanSink
placeGrillMeat
simplement parce que vous avez une classe qui fait exactement cela et quelques autres choses, mais rien comme les autres méthodes? Ou le diviseriez-vous en deux interfaces plus cohésives, telles que:Note de déclaration d'interface
Une définition d'interface devrait de préférence être seule dans une unité distincte, mais si elle doit absolument vivre avec l'appelant ou l'implémenteur, elle devrait vraiment l'être avec l'appelant. Sinon, l'appelant obtient une dépendance immédiate vis-à-vis de l'implémenteur qui va complètement à l'encontre de l'objectif des interfaces. Voir aussi: Déclarer l'interface dans le même fichier que la classe de base, est-ce une bonne pratique? sur les programmeurs et pourquoi devrions-nous placer des interfaces avec les classes qui les utilisent plutôt que celles qui les implémentent? sur StackOverflow.
la source
ICook
au lieu de typeSomeCookImplementor
, comme les mandats DIP, alors il ne pas dépendreSomeCookImplementor
.Vous confondez le mot «client» tel qu'il est utilisé dans les documents Gang of Four avec un «client» comme consommateur de service.
Un "client", comme le veulent les définitions de Gang of Four, est une classe qui implémente une interface. Si la classe A implémente l'interface B, alors ils disent que A est un client de B. Sinon, l'expression "les clients ne devraient pas être forcés d'implémenter des interfaces qu'ils n'utilisent pas" n'aurait pas de sens puisque "les clients" (comme chez les consommateurs) ne 'implémentez rien. L'expression n'a de sens que lorsque vous voyez "client" comme "implémenteur".
Si "client" signifiait une classe qui "consomme" (appelle) les méthodes d'une autre classe qui implémente la grande interface, alors en appelant les deux méthodes qui vous intéressent et en ignorant le reste, cela suffirait pour vous garder découplé du reste de les méthodes que vous n'utilisez pas.
L'esprit du principe est d'éviter que le "client" (la classe implémentant l'interface) n'ait à implémenter des méthodes factices afin de se conformer à l'ensemble de l'interface lorsqu'il ne se soucie que d'un ensemble de méthodes liées.
Il vise également à avoir le moins de couplage possible afin que les modifications apportées en un seul endroit entraînent le moins d'impact. En séparant les interfaces, vous réduisez le couplage.
Ce problème apparaît lorsque l'interface fait trop et a des méthodes qui devraient être divisées en plusieurs interfaces au lieu d'une seule.
Vos deux exemples de code sont OK . C'est seulement que dans le second, vous supposez que "client" signifie "une classe qui consomme / appelle les services / méthodes offerts par une autre classe".
Je ne trouve aucune contradiction dans les concepts expliqués dans les trois liens que vous avez donnés.
Gardez simplement à l'esprit que "client" est un implémenteur , dans SOLID talk.
la source
Le FAI consiste à isoler le client de mieux connaître le service qu'il ne devrait en savoir (le protéger contre des changements indépendants, par exemple). Votre deuxième définition est correcte. À ma lecture, un seul de ces trois articles suggère le contraire ( le premier ) et c'est tout simplement faux. (Edit: Non, pas mal, juste trompeur.)
La première définition est beaucoup plus étroitement liée au LSP.
la source