Tout ce que vous devez savoir sur les principes solides en Java

Dans cet article, vous apprendrez en détail ce que sont les principes solides en java avec des exemples et leur importance avec des exemples réels.

Dans le monde de (POO), il existe de nombreuses lignes directrices, modèles ou principes de conception. Cinq de ces principes sont généralement regroupés et sont connus sous l'acronyme SOLID. Bien que chacun de ces cinq principes décrive quelque chose de spécifique, ils se chevauchent également de sorte que l'adoption de l'un d'eux implique ou conduit à en adopter un autre. Dans cet article, nous allons comprendre les principes SOLID en Java.



Histoire des principes SOLID en Java

Robert C. Martin a donné cinq principes de conception orientée objet, et l'acronyme «S.O.L.I.D» est utilisé pour cela. Lorsque vous utilisez tous les principes de S.O.L.I.D de manière combinée, il devient plus facile pour vous de développer un logiciel qui peut être géré facilement. Les autres fonctionnalités de l'utilisation de S.O.L.I.D sont:



  • Cela évite les odeurs de code.
  • Code réfracteur rapidement.
  • Peut faire du développement logiciel adaptatif ou agile.

Lorsque vous utilisez le principe de S.O.L.I.D dans votre codage, vous commencez à écrire le code à la fois efficace et efficient.



Quelle est la signification de S.O.L.I.D?

Solid représente cinq principes de Java qui sont:

  • S : Principe de responsabilité unique
  • OU : Principe ouvert-fermé
  • L : Principe de substitution de Liskov
  • je : Principe de séparation des interfaces
  • : Principe d'inversion des dépendances

Dans ce blog, nous discuterons en détail des cinq principes SOLID de Java.



Principe de responsabilité unique en Java

Ça dit quoi?

Robert C. Martin le décrit comme une classe ne devrait avoir qu'une seule et unique responsabilité.

Selon le principe de responsabilité unique, il ne devrait y avoir qu'une seule raison pour laquelle une classe doit être modifiée. Cela signifie qu'une classe doit avoir une tâche à accomplir. Ce principe est souvent qualifié de subjectif.

Le principe peut être bien compris avec un exemple. Imaginez qu'il existe une classe qui effectue les opérations suivantes.

  • Connecté à une base de données

  • Lire certaines données des tables de la base de données

  • Enfin, écrivez-le dans un fichier.

Avez-vous imaginé le scénario? Ici, la classe a plusieurs raisons de changer, et peu d'entre elles sont la modification de la sortie du fichier, l'adoption d'une nouvelle base de données. Lorsque nous parlons de responsabilité à principe unique, nous dirions, il y a trop de raisons pour que la classe change, par conséquent, cela ne rentre pas correctement dans le principe de responsabilité unique.

Par exemple, une classe Automobile peut démarrer ou s'arrêter d'elle-même, mais la tâche de lavage appartient à la classe CarWash. Dans un autre exemple, une classe Book a des propriétés pour stocker son propre nom et texte. Mais la tâche d'impression du livre doit appartenir à la classe Book Printer. La classe Book Printer peut imprimer sur la console ou sur un autre support, mais ces dépendances sont supprimées de la classe Book

Pourquoi ce principe est-il requis?

Lorsque le principe de responsabilité unique est suivi, les tests sont plus faciles. Avec une seule responsabilité, la classe aura moins de cas de test. Moins de fonctionnalités signifie également moins de dépendances avec d'autres classes. Cela conduit à une meilleure organisation du code car les classes plus petites et bien conçues sont plus faciles à rechercher.

Un exemple pour clarifier ce principe:

Supposons que vous soyez invité à implémenter un service UserSetting dans lequel l'utilisateur peut modifier les paramètres mais avant cela, l'utilisateur doit être authentifié. Une façon de mettre en œuvre cela serait:

public class UserSettingService {public void changeEmail (User user) {if (checkAccess (user)) {// Accorder une option pour changer}} public boolean checkAccess (User user) {// Vérifier si l'utilisateur est valide. }}

Tout semble bon jusqu'à ce que vous souhaitiez réutiliser le code checkAccess à un autre endroit OU que vous souhaitiez apporter des modifications à la façon dont checkAccess est effectué. Dans les 2 cas, vous finiriez par changer la même classe et dans le premier cas, vous devrez également utiliser UserSettingService pour vérifier l'accès.
Une façon de corriger cela consiste à décomposer le UserSettingService en UserSettingService et SecurityService. Et déplacez le code checkAccess dans SecurityService.

public class UserSettingService {public void changeEmail (User user) {if (SecurityService.checkAccess (user)) {// Accorder une option pour changer}}} public class SecurityService {public static boolean checkAccess (User user) {// vérifier l'accès. }}

Principe ouvert fermé en Java

Robert C. Martin le décrit comme les composants logiciels doivent être ouverts pour extension, mais fermés pour modification.

Pour être précis, selon ce principe, une classe doit être écrite de telle manière qu'elle accomplisse parfaitement son travail sans supposer qu'à l'avenir, les gens viendront simplement la changer. Par conséquent, la classe devrait rester fermée pour modification, mais elle devrait avoir la possibilité d'être étendue. Les moyens d'étendre la classe comprennent:

  • Hériter de la classe

  • Écraser les comportements requis de la classe

  • Extension de certains comportements de la classe

Un excellent exemple de principe ouvert-fermé peut être compris à l'aide des navigateurs. Vous souvenez-vous d'avoir installé des extensions dans votre navigateur Chrome?

La fonction de base du navigateur Chrome est de surfer sur différents sites. Voulez-vous vérifier la grammaire lorsque vous écrivez un e-mail à l'aide du navigateur Chrome? Si oui, vous pouvez simplement utiliser l'extension Grammarly, elle vous permet de vérifier la grammaire du contenu.

Ce mécanisme dans lequel vous ajoutez des éléments pour augmenter les fonctionnalités du navigateur est une extension. Par conséquent, le navigateur est un exemple parfait de fonctionnalité ouverte pour extension mais fermée pour modification. En termes simples, vous pouvez améliorer la fonctionnalité en ajoutant / installant des plugins sur votre navigateur, mais vous ne pouvez rien créer de nouveau.

Pourquoi ce principe est-il nécessaire?

OCP est important car les classes peuvent nous parvenir via des bibliothèques tierces. Nous devrions pouvoir étendre ces classes sans nous inquiéter si ces classes de base peuvent supporter nos extensions. Mais l'héritage peut conduire à des sous-classes qui dépendent de l'implémentation de la classe de base. Pour éviter cela, l'utilisation d'interfaces est recommandée. Cette abstraction supplémentaire conduit à un couplage lâche.

Disons que nous devons calculer des zones de différentes formes. Nous commençons par créer une classe pour notre première forme Rectanglequi a 2 attributs de longueur& largeur.

public class Rectangle {public double length public double width}

Ensuite, nous créons une classe pour calculer l'aire de ce rectanglequi a une méthode CalculateRectangleAreaqui prend le rectanglecomme paramètre d'entrée et calcule sa surface.

Public class AreaCalculator {public double CalculateRectangleArea (Rectangle rectangle) {return rectangle.length * rectangle.width}}

Jusqu'ici tout va bien. Disons maintenant que nous obtenons notre deuxième cercle de forme. Nous créons donc rapidement un nouveau cercle de classeavec un rayon d'attribut unique.

public class Circle {public double radius}

Ensuite, nous modifions Areacalculatorclasse pour ajouter des calculs de cercle via une nouvelle méthode CalculateCircleaArea ()

public class AreaCalculator {public double CalculateRectangleArea (Rectangle rectangle) {return rectangle.length * rectangle.width} public double CalculateCircleArea (Circle circle) {return (22/7) * circle.radius * circle.radius}}

Cependant, notez qu'il y avait des failles dans la façon dont nous avons conçu notre solution ci-dessus.

Disons que nous avons un nouveau pentagone de forme. Dans ce cas, nous finirons à nouveau par modifier la classe AreaCalculator. Au fur et à mesure que les types de formes augmentent, cela devient plus compliqué car AreaCalculator continue de changer et tous les utilisateurs de cette classe devront continuer à mettre à jour leurs bibliothèques qui contiennent AreaCalculator. En conséquence, la classe AreaCalculator ne sera pas référencée (finalisée) avec certitude, car chaque fois qu'une nouvelle forme apparaît, elle sera modifiée. Donc, cette conception n'est pas fermée pour modification.

AreaCalculator devra continuer à ajouter sa logique de calcul dans les nouvelles méthodes. Nous n'étendons pas vraiment la portée des formes, mais nous faisons simplement une solution pièce par pièce (petit à petit) pour chaque forme ajoutée.

Modification de la conception ci-dessus pour se conformer au principe ouvert / fermé:

Voyons maintenant un design plus élégant qui résout les défauts de la conception ci-dessus en adhérant au principe ouvert / fermé. Nous allons tout d'abord rendre le design extensible. Pour cela, nous devons d'abord définir un type de base Shape et faire en sorte que Circle & Rectangle implémente l'interface Shape.

comment installer php sur windows 10
interface publique Shape {public double CalculateArea ()} public class Rectangle implements Shape {double longueur double largeur public double CalculateArea () {return length * width}} public class Circle implements Shape {public double rayon public double CalculateArea () {return (22 / 7) * rayon * rayon}}

Il existe une forme d'interface de base. Toutes les formes implémentent désormais l'interface de base Shape. L'interface Shape a une méthode abstraite CalculateArea (). Le cercle et le rectangle fournissent tous deux leur propre implémentation remplacée de la méthode CalculateArea () en utilisant leurs propres attributs.
Nous avons apporté un degré d'extensibilité car les formes sont maintenant une instance des interfaces Shape. Cela nous permet d'utiliser Shape au lieu de classes individuelles
Le dernier point ci-dessus mentionné consommateur de ces formes. Dans notre cas, le consommateur sera la classe AreaCalculator qui ressemblerait maintenant à ceci.

Classe publique AreaCalculator {double public CalculateShapeArea (Shape shape) {return shape.calculateArea ()}}

Cet AreaCalculatorclass supprime désormais complètement nos défauts de conception mentionnés ci-dessus et donne une solution propre qui adhère au principe ouvert-fermé. Passons à d'autres principes SOLID en Java

Principe de substitution de Liskov en Java

Robert C. Martin le décrit comme les types dérivés doivent être complètement substituables à leurs types de base.

Le principe de substitution de Liskov suppose que q (x) est une propriété, prouvable sur les entités de x qui appartiennent au type T. Maintenant, selon ce principe, le q (y) devrait maintenant être prouvable pour les objets y qui appartiennent au type S, et le S est en fait un sous-type de T. Êtes-vous maintenant confus et ne savez-vous pas ce que signifie réellement le principe de substitution de Liskov? La définition de celui-ci peut être un peu complexe, mais en fait, c'est assez facile. La seule chose est que chaque sous-classe ou classe dérivée doit être substituable à leur classe parent ou de base.

Vous pouvez dire que c'est un principe unique orienté objet. Le principe peut encore être simplifié par un type d'enfant d'un type de parent particulier sans faire de complication ou faire exploser les choses devrait avoir la capacité de remplacer ce parent.Ce principe est étroitement lié au principe de substitution de Liskov.

Pourquoi ce principe est-il nécessaire?

Cela évite une mauvaise utilisation de l'héritage. Cela nous aide à nous conformer à la relation «est-un». On peut aussi dire que les sous-classes doivent remplir un contrat défini par la classe de base. En ce sens, il est lié àConception par contratcela a été décrit pour la première fois par Bertrand Meyer. Par exemple, il est tentant de dire qu’un cercle est un type d’ellipse, mais que les cercles n’ont pas deux foyers ou axes majeurs / mineurs.

Le LSP est généralement expliqué en utilisant l'exemple du carré et du rectangle. si nous supposons une relation ISA entre Square et Rectangle. Ainsi, nous appelons «le carré est un rectangle». Le code ci-dessous représente la relation.

public class Rectangle {private int length private int width public int getLength () {return length} public void setLength (int length) {this.length = length} public int getBreadth () {return width} public void setBreadth (int width) { this.breadth = width} public int getArea () {return this.length * this.breadth}}

Vous trouverez ci-dessous le code de Square. Notez que Square étend le rectangle.

public class Square s'étend Rectangle {public void setBreadth (int width) {super.setBreadth (width) super.setLength (width)} public void setLength (int length) {super.setLength (length) super.setBreadth (length)}}

Dans ce cas, nous essayons d'établir une relation ISA entre Square et Rectangle de telle sorte qu'appeler «Square is a Rectangle» dans le code ci-dessous commencerait à se comporter de manière inattendue si une instance de Square est passée. Une erreur d'assertion sera lancée dans le cas de la vérification de «Area» et de la vérification de «Breadth», bien que le programme s'arrête lorsque l'erreur d'assertion est lancée en raison de l'échec de la vérification de la zone.

public class LSPDemo {public void CalculateArea (Rectangle r) {r.setBreadth (2) r.setLength (3) assert r.getArea () == 6: printError ('area', r) assert r.getLength () == 3: printError ('length', r) assert r.getBreadth () == 2: printError ('width', r)} private String printError (String errorIdentifer, Rectangle r) {return 'Valeur inattendue de' + errorIdentifer + ' par exemple de '+ r.getClass (). getName ()} public static void main (String [] args) {LSPDemo lsp = new LSPDemo () // Une instance de Rectangle est passée lsp.calculateArea (new Rectangle ()) // Une instance de Square est passée à lsp.calculateArea (new Square ())}}

La classe démontre le principe de substitution de Liskov (LSP) Selon le principe, les fonctions qui utilisent des références aux classes de base doivent pouvoir utiliser des objets de classe dérivée sans le savoir.

Ainsi, dans l'exemple ci-dessous, la fonction CalculateArea qui utilise la référence de «Rectangle» devrait pouvoir utiliser les objets de la classe dérivée telle que Square et remplir l'exigence posée par la définition de Rectangle. Il convient de noter que selon la définition de Rectangle, ce qui suit doit toujours être vrai compte tenu des données ci-dessous:

  1. La longueur doit toujours être égale à la longueur transmise en entrée de la méthode setLength
  2. La largeur doit toujours être égale à la largeur transmise en entrée à la méthode, setBreadth
  3. La surface doit toujours être égale au produit de la longueur et de la largeur

Dans le cas où nous essayons d'établir une relation ISA entre Square et Rectangle de telle sorte que nous appelons 'Square is a Rectangle', le code ci-dessus commencerait à se comporter de manière inattendue si une instance de Square est passée Une erreur d'assertion sera lancée en cas de vérification de la zone et de la vérification pour la largeur, bien que le programme se terminera lorsque l'erreur d'assertion est levée en raison d'un échec de la vérification de zone.

La classe Square n'a pas besoin de méthodes telles que setBreadth ou setLength. La classe LSPDemo aurait besoin de connaître les détails des classes dérivées de Rectangle (telles que Square) pour coder de manière appropriée afin d'éviter de générer une erreur. Le changement dans le code existant brise le principe ouvert-fermé en premier lieu.

Principe de séparation des interfaces

Robert C. Martin le décrit comme le fait que les clients ne devraient pas être forcés d'implémenter des méthodes inutiles qu'ils n'utiliseront pas.

SelonPrincipe de ségrégation d'interfaceun client, peu importe ce qui ne devrait jamais être forcé d'implémenter une interface qu'il n'utilise pas ou le client ne devrait jamais être obligé de dépendre d'une méthode, qui n'est pas utilisée par eux. Donc, fondamentalement, les principes de ségrégation d'interface que vous préférez le interfaces, qui sont petites mais spécifiques au client au lieu d'une interface monolithique et plus grande. En bref, il serait mauvais pour vous de forcer le client à dépendre d'une certaine chose, dont il n'a pas besoin.

Par exemple, une interface de journalisation unique pour l'écriture et la lecture des journaux est utile pour une base de données mais pas pour une console. La lecture des journaux n'a aucun sens pour un enregistreur de console. Continuez avec cet article sur les principes SOLID en Java.

Pourquoi ce principe est-il nécessaire?

Supposons qu'il existe une interface de restaurant qui contient des méthodes pour accepter les commandes des clients en ligne, des clients commutés ou téléphoniques et des clients sans rendez-vous. Il contient également des méthodes de traitement des paiements en ligne (pour les clients en ligne) et des paiements en personne (pour les clients sans rendez-vous ainsi que pour les clients par téléphone lorsque leur commande est livrée à domicile).

Maintenant, créons une interface Java pour Restaurant et appelons-la RestaurantInterface.java.

interface publique RestaurantInterface {public void acceptOnlineOrder () public void takeTelephoneOrder () public void payOnline () public void walkInCustomerOrder () public void payInPerson ()}

Il y a 5 méthodes définies dans RestaurantInterface qui sont pour accepter la commande en ligne, prendre la commande téléphonique, accepter les commandes d'un client sans rendez-vous, accepter le paiement en ligne et accepter le paiement en personne.

Commençons par implémenter RestaurantInterface pour les clients en ligne en tant que OnlineClientImpl.java

public class OnlineClientImpl implémente RestaurantInterface {public void acceptOnlineOrder () {// logique de commande en ligne} public void takeTelephoneOrder () {// Non applicable pour la commande en ligne throw new UnsupportedOperationException ()} public void payOnline () {// logique de paiement online} public void walkInCustomerOrder () {// Non applicable pour la commande en ligne throw new UnsupportedOperationException ()} public void payInPerson () {// Non applicable pour la commande en ligne throw new UnsupportedOperationException ()}}
  • Étant donné que le code ci-dessus (OnlineClientImpl.java) est destiné aux commandes en ligne, lancez l'exception UnsupportedOperationException.

  • Les clients en ligne, téléphoniques et sans rendez-vous utilisent l'implémentation de RestaurantInterface spécifique à chacun d'eux.

  • Les classes d'implémentation du client Telephonic et du client Walk-in auront des méthodes non prises en charge.

  • Puisque les 5 méthodes font partie de RestaurantInterface, les classes d'implémentation doivent toutes les implémenter.

  • Les méthodes que chacune des classes d'implémentation lèvent UnsupportedOperationException. Comme vous pouvez le voir clairement, la mise en œuvre de toutes les méthodes est inefficace.

  • Tout changement dans l'une des méthodes de RestaurantInterface sera propagé à toutes les classes d'implémentation. La maintenance du code commence alors à devenir vraiment lourde et les effets de régression des changements continueront d'augmenter.

    javascript obtenir la taille du tableau
  • RestaurantInterface.java enfreint le principe de responsabilité unique car la logique des paiements ainsi que celle du placement des commandes sont regroupées dans une seule interface.

Pour surmonter les problèmes mentionnés ci-dessus, nous appliquons le principe de séparation d'interface pour refactoriser la conception ci-dessus.

  1. Séparez les fonctionnalités de paiement et de commande en deux interfaces lean distinctes, PaymentInterface.java et OrderInterface.java.

  2. Chacun des clients utilise une implémentation de PaymentInterface et OrderInterface. Par exemple - OnlineClient.java utilise OnlinePaymentImpl et OnlineOrderImpl et ainsi de suite.

  3. Le principe de responsabilité unique est désormais joint en tant qu'interface de paiement (PaymentInterface.java) et interface de commande (OrderInterface).

  4. Le changement de l'une des interfaces de commande ou de paiement n'affecte pas l'autre. Ils sont désormais indépendants.Il ne sera pas nécessaire de faire une implémentation factice ou de lancer une exception UnsupportedOperationException car chaque interface n'a que des méthodes qu'elle utilisera toujours.

Après avoir appliqué le FAI

Principe d'inversion de dépendance

Robert C. Martin le décrit car il dépend d'abstractions et non de concrétions. Selon lui, le module de haut niveau ne doit jamais s'appuyer sur un module de bas niveau. par exemple

Vous vous rendez dans un magasin local pour acheter quelque chose et vous décidez de le payer en utilisant votre carte de débit. Ainsi, lorsque vous donnez votre carte au greffier pour effectuer le paiement, le greffier ne prend pas la peine de vérifier quel type de carte vous avez donné.

Même si vous avez donné une carte Visa, il ne mettra pas de machine Visa pour glisser votre carte. Le type de carte de crédit ou de débit que vous avez pour payer n'a même pas d'importance, il le fera simplement glisser. Ainsi, dans cet exemple, vous pouvez voir que vous et le greffier êtes dépendants de l'abstraction de la carte de crédit et que vous ne vous inquiétez pas des détails de la carte. C'est ce qu'est un principe d'inversion de dépendance.

Pourquoi ce principe est-il nécessaire?

Il permet à un programmeur de supprimer les dépendances codées en dur afin que l'application devienne faiblement couplée et extensible.

public class Student {adresse privée address public Student () {address = new Address ()}}

Dans l'exemple ci-dessus, la classe Student requiert un objet Address et est responsable de l'initialisation et de l'utilisation de l'objet Address. Si la classe Address est modifiée à l'avenir, nous devons également apporter des modifications à la classe Student. Cela rend le couplage étroit entre les objets Student et Address. Nous pouvons résoudre ce problème à l'aide du modèle de conception d'inversion de dépendance. c'est-à-dire que l'objet Address sera implémenté indépendamment et sera fourni à Student lorsque Student est instancié à l'aide d'une inversion de dépendance basée sur un constructeur ou basée sur un setter.

Avec cela, nous arrivons à la fin de ces principes SOLID en Java.

Vérifiez par Edureka, une entreprise d'apprentissage en ligne de confiance avec un réseau de plus de 250 000 apprenants satisfaits répartis dans le monde entier. Le cours de formation et de certification Java J2EE et SOA d'Edureka est conçu pour les étudiants et les professionnels qui souhaitent devenir développeur Java. Le cours est conçu pour vous donner une longueur d'avance dans la programmation Java et vous former aux concepts Java de base et avancés ainsi qu'à divers frameworks Java tels que Hibernate & Spring.

Vous avez une question pour nous? Veuillez le mentionner dans la section commentaires de ce blog «SOLID Principles in Java» et nous vous répondrons dans les plus brefs délais.