paint-brush
Maîtriser des principes SOLIDES comme le dos de votre main en seulement 8 minutes !par@arulvalananto
26,256 lectures
26,256 lectures

Maîtriser des principes SOLIDES comme le dos de votre main en seulement 8 minutes !

par Arul Valan Anto11m2023/07/07
Read on Terminal Reader

Trop long; Pour lire

Dans ce blog, je vais démontrer la mise en œuvre des principes SOLID dans une application React. À la fin de cet article, vous maîtriserez pleinement les principes SOLID.
featured image - Maîtriser des principes SOLIDES comme le dos de votre main en seulement 8 minutes !
Arul Valan Anto HackerNoon profile picture
0-item

Dans ce blog, je vais démontrer la mise en œuvre des principes SOLID dans une application React. À la fin de cet article, vous maîtriserez pleinement les principes SOLID. Avant de commencer, permettez-moi de vous donner une brève introduction à ces principes.

Que sont les principes SOLID ?

Les principes SOLID sont cinq principes de conception qui nous aident à garder notre application réutilisable, maintenable, évolutive et faiblement couplée. Les principes SOLID sont :


  • Principe de responsabilité unique
  • Principe Ouvert-Fermé
  • Principe de substitution de Liskov
  • Principe de ségrégation des interfaces
  • Principe d'inversion de dépendance


Bon, examinons chacun de ces principes individuellement. J'utilise React comme exemple, mais les concepts de base sont similaires à d'autres langages de programmation et frameworks.

Principe de responsabilité unique

"Un module doit être responsable devant un, et un seul, acteur." - Wikipédia.


Le principe de responsabilité unique stipule qu'un composant doit avoir un objectif ou une responsabilité clairs. Il doit se concentrer sur une fonctionnalité ou un comportement spécifique et éviter d'entreprendre des tâches sans rapport. Suivre SRP rend les composants plus ciblés, modulaires et faciles à comprendre et à modifier. Voyons la mise en œuvre réelle.


 // ❌ Bad Practice: Component with Multiple Responsibilities const Products = () => { return ( <div className="products"> {products.map((product) => ( <div key={product?.id} className="product"> <h3>{product?.name}</h3> <p>${product?.price}</p> </div> ))} </div> ); };


Dans l'exemple ci-dessus, le composant Products enfreint le principe de responsabilité unique en assumant plusieurs responsabilités. Il gère l'itération des produits et gère le rendu de l'interface utilisateur pour chaque produit. Cela peut rendre le composant difficile à comprendre, à maintenir et à tester à l'avenir.

Au lieu de cela, faites ceci pour adhérer au SRP :

 // ✅ Good Practice: Separating Responsibilities into Smaller Components import Product from './Product'; import products from '../../data/products.json'; const Products = () => { return ( <div className="products"> {products.map((product) => ( <Product key={product?.id} product={product} /> ))} </div> ); }; // Product.js // Separate component responsible for rendering the product details const Product = ({ product }) => { return ( <div className="product"> <h3>{product?.name}</h3> <p>${product?.price}</p> </div> ); };


Cette séparation garantit que chaque composant a une responsabilité unique, ce qui les rend plus faciles à comprendre, à tester et à entretenir.

Principe Ouvert-Fermé

"Les entités logicielles (classes, modules, fonctions, etc.) doivent être ouvertes pour extension, mais fermées pour modification." - Wikipédia.


Le principe ouvert-fermé souligne que les composants doivent être ouverts pour l'extension (peut ajouter de nouveaux comportements ou fonctionnalités) mais fermés pour modification (le code existant doit rester inchangé). Ce principe encourage la création de code résilient au changement, modulaire et facilement maintenable. Voyons la mise en œuvre réelle.


 // ❌ Bad Practice: Violating the Open-Closed Principle // Button.js // Existing Button component const Button = ({ text, onClick }) => { return ( <button onClick={onClick}> {text} </button> ); } // Button.js // Modified Existing Button component with additional icon prop (modification) const Button = ({ text, onClick, icon }) => { return ( <button onClick={onClick}> <i className={icon} /> <span>{text}</span> </button> ); } // Home.js // 👇 Avoid: Modified existing component prop const Home = () => { const handleClick= () => {}; return ( <div> {/* ❌ Avoid this */} <Button text="Submit" onClick={handleClick} icon="fas fa-arrow-right" /> </div> ); }


Dans l'exemple ci-dessus, nous modifions le composant Button existant en ajoutant un accessoire icon . La modification d'un composant existant pour répondre à de nouvelles exigences enfreint le principe ouvert-fermé. Ces changements fragilisent le composant et introduisent le risque d'effets secondaires involontaires lorsqu'il est utilisé dans différents contextes.

Au lieu de cela, faites ceci :

 // ✅ Good Practice: Open-Closed Principle // Button.js // Existing Button functional component const Button = ({ text, onClick }) => { return ( <button onClick={onClick}> {text} </button> ); } // IconButton.js // IconButton component // ✅ Good: You have not modified anything here. const IconButton = ({ text, icon, onClick }) => { return ( <button onClick={onClick}> <i className={icon} /> <span>{text}</span> </button> ); } const Home = () => { const handleClick = () => { // Handle button click event } return ( <div> <Button text="Submit" onClick={handleClick} /> {/* <IconButton text="Submit" icon="fas fa-heart" onClick={handleClick} /> </div> ); }


Dans l'exemple ci-dessus, nous créons un composant fonctionnel IconButton séparé. Le composant IconButton encapsule le rendu d'un bouton d'icône sans modifier le composant Button existant. Il adhère au principe ouvert-fermé en étendant la fonctionnalité par la composition plutôt que par la modification.

Principe de substitution de Liskov

"Les objets de sous-type doivent être substituables aux objets de supertype" - Wikipedia.


Le principe de substitution de Liskov (LSP) est un principe fondamental de la programmation orientée objet qui met l'accent sur la nécessité de substituabilité des objets au sein d'une hiérarchie. Dans le contexte des composants React, LSP promeut l'idée que les composants dérivés devraient pouvoir remplacer leurs composants de base sans affecter l'exactitude ou le comportement de l'application. Voyons la mise en œuvre réelle.


 // ⚠️ Bad Practice // This approach violates the Liskov Substitution Principle as it modifies // the behavior of the derived component, potentially resulting in unforeseen // problems when substituting it for the base Select component. const BadCustomSelect = ({ value, iconClassName, handleChange }) => { return ( <div> <i className={iconClassName}></i> <select value={value} onChange={handleChange}> <options value={1}>One</options> <options value={2}>Two</options> <options value={3}>Three</options> </select> </div> ); }; const LiskovSubstitutionPrinciple = () => { const [value, setValue] = useState(1); const handleChange = (event) => { setValue(event.target.value); }; return ( <div> {/** ❌ Avoid this */} {/** Below Custom Select doesn't have the characteristics of base `select` element */} <BadCustomSelect value={value} handleChange={handleChange} /> </div> );


Dans l'exemple ci-dessus, nous avons un composant BadCustomSelect destiné à servir d'entrée de sélection personnalisée dans React. Cependant, il viole le principe de substitution de Liskov (LSP) car il limite le comportement de l'élément select de base.

Au lieu de cela, faites ceci :

 // ✅ Good Practice // This component follows the Liskov Substitution Principle and allows the use of select's characteristics. const CustomSelect = ({ value, iconClassName, handleChange, ...props }) => { return ( <div> <i className={iconClassName}></i> <select value={value} onChange={handleChange} {...props}> <options value={1}>One</options> <options value={2}>Two</options> <options value={3}>Three</options> </select> </div> ); }; const LiskovSubstitutionPrinciple = () => { const [value, setValue] = useState(1); const handleChange = (event) => { setValue(event.target.value); }; return ( <div> {/* ✅ This CustomSelect component follows the Liskov Substitution Principle */} <CustomSelect value={value} handleChange={handleChange} defaultValue={1} /> </div> ); };


Dans le code révisé, nous avons un composant CustomSelect destiné à étendre les fonctionnalités de l'élément select standard dans React. Le composant accepte des accessoires tels que value , iconClassName , handleChange et des accessoires supplémentaires à l'aide de l'opérateur de propagation ...props . En permettant l'utilisation des caractéristiques de l'élément select et en acceptant des accessoires supplémentaires, le composant CustomSelect suit le principe de substitution de Liskov (LSP).

Principe de séparation des interfaces

"Aucun code ne devrait être contraint de dépendre de méthodes qu'il n'utilise pas." - Wikipédia.


Le principe de ségrégation des interfaces (ISP) suggère que les interfaces doivent être ciblées et adaptées aux besoins spécifiques des clients plutôt que d'être trop larges et d'obliger les clients à mettre en œuvre des fonctionnalités inutiles. Voyons la mise en œuvre réelle.


 // ❌ Avoid: disclose unnecessary information for this component // This introduces unnecessary dependencies and complexity for the component const ProductThumbnailURL = ({ product }) => { return ( <div> <img src={product.imageURL} alt={product.name} /> </div> ); }; // ❌ Bad Practice const Products = ({ product }) => { return ( <div> <ProductThumbnailURL product={product} /> <h4>{product?.name}</h4> <p>{product?.description}</p> <p>{product?.price}</p> </div> ); }; const Products = () => { return ( <div> {products.map((product) => ( <Product key={product.id} product={product} /> ))} </div> ); }


Dans l'exemple ci-dessus, nous transmettons tous les détails du produit au composant ProductThumbnailURL , même s'il ne l'exige pas. Cela ajoute des risques et une complexité inutiles au composant et enfreint le principe de séparation des interfaces (ISP).

Refactorisons pour adhérer au FAI :

 // ✅ Good: reducing unnecessary dependencies and making // the codebase more maintainable and scalable. const ProductThumbnailURL = ({ imageURL, alt }) => { return ( <div> <img src={imageURL} alt={alt} /> </div> ); }; // ✅ Good Practice const Products = ({ product }) => { return ( <div> <ProductThumbnailURL imageURL={product.imageURL} alt={product.name} /> <h4>{product?.name}</h4> <p>{product?.description}</p> <p>{product?.price}</p> </div> ); }; const Products = () => { return ( <div> {products.map((product) => ( <Product key={product.id} product={product} /> ))} </div> ); };


Dans le code révisé, le composant ProductThumbnailURL ne reçoit que les informations requises au lieu de tous les détails du produit. Il prévient les risques inutiles et favorise le principe de ségrégation des interfaces (ISP).

Principe d'inversion de dépendance

"Une entité devrait dépendre d'abstractions, pas de concrétions" - Wikipedia.


Le principe d'inversion de dépendance (DIP) souligne que les composants de haut niveau ne doivent pas dépendre des composants de bas niveau. Ce principe favorise le couplage lâche et la modularité et facilite la maintenance des systèmes logiciels. Voyons la mise en œuvre réelle.


 // ❌ Bad Practice // This component follows concretion instead of abstraction and // breaks Dependency Inversion Principle const CustomForm = ({ children }) => { const handleSubmit = () => { // submit operations }; return <form onSubmit={handleSubmit}>{children}</form>; }; const DependencyInversionPrinciple = () => { const [email, setEmail] = useState(); const handleChange = (event) => { setEmail(event.target.value); }; const handleFormSubmit = (event) => { // submit business logic here }; return ( <div> {/** ❌ Avoid: tightly coupled and hard to change */} <BadCustomForm> <input type="email" value={email} onChange={handleChange} name="email" /> </BadCustomForm> </div> ); };


Le composant CustomForm est étroitement couplé à ses enfants, ce qui empêche la flexibilité et rend difficile la modification ou l'extension de son comportement.

Au lieu de cela, faites ceci :

 // ✅ Good Practice // This component follows abstraction and promotes Dependency Inversion Principle const AbstractForm = ({ children, onSubmit }) => { const handleSubmit = (event) => { event.preventDefault(); onSubmit(); }; return <form onSubmit={handleSubmit}>{children}</form>; }; const DependencyInversionPrinciple = () => { const [email, setEmail] = useState(); const handleChange = (event) => { setEmail(event.target.value); }; const handleFormSubmit = () => { // submit business logic here }; return ( <div> {/** ✅ Use the abstraction instead */} <AbstractForm onSubmit={handleFormSubmit}> <input type="email" value={email} onChange={handleChange} name="email" /> <button type="submit">Submit</button> </AbstractForm> </div> ); };


Dans le code révisé, nous introduisons le composant AbstractForm , qui agit comme une abstraction pour le formulaire. Il reçoit la fonction onSubmit en tant que prop et gère la soumission du formulaire. Cette approche nous permet d'échanger ou d'étendre facilement le comportement du formulaire sans modifier le composant de niveau supérieur.

Conclusion

Les principes SOLID fournissent des lignes directrices qui permettent aux développeurs de créer des solutions logicielles bien conçues, maintenables et extensibles. En adhérant à ces principes, les développeurs peuvent atteindre la modularité, la réutilisabilité du code, la flexibilité et la complexité réduite du code.


J'espère que ce blog vous a fourni des informations précieuses et vous a inspiré à appliquer ces principes dans vos projets React existants ou à venir.


Reste curieux; continuez à coder !


Référence:


Également publié ici .