”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 掌握 SOLID 原则:干净高效的代码

掌握 SOLID 原则:干净高效的代码

发布于2024-11-08
浏览:861

Dans le monde du développement, il y a un grand fossé entre écrire du code et écrire du bon code. Tu sais, ce code que tu peux modifier dans 6 mois sans avoir à tout réécrire, ce code qui ne t’oblige pas à passer des heures à le comprendre avant de le débugger.

Et si je te disais qu’il existe une méthode pour structurer ton code de manière à le rendre facilement maintenable, modulaire, et sans prise de tête ? Accueillons sans plus attendre les principes SOLID.

Ces cinq principes, introduits par l'incontournable Robert C. Martin, aka Uncle Bob, montrent comment organiser tes classes et tes méthodes pour qu’elles puissent évoluer sans s’effondrer.

Tu es prêt à devenir un développeur plus ? SOLID ? que jamais ? On y va. ?


1. Le * S * ➡️ Single Responsibility Principle

"Une classe ne doit avoir qu'une seule responsabilité."

L'idée est simple : chaque classe doit avoir une seule raison de changer. Cela signifie qu'une classe ne doit s'occuper que d'une seule tâche ou d'une seule fonctionnalité. Si tu mélanges plusieurs responsabilités dans une même classe, tu finiras par complexifier ton code et rendre les modifications plus risquées.

Exemple avec Symfony

Prenons l'exemple d'une classe qui gère à la fois la validation d'un formulaire d'utilisateur et l'envoi d'emails de confirmation. Voici un exemple de ce qu'il ne faut pas faire :

Maîtriser les principes SOLID : pour un code propre et performant

Ici, la classe UserService viole le principe de responsabilité unique. Elle gère à la fois la création de l'utilisateur, la validation des données, et l'envoi d'un email. Chaque fois que l'une de ces responsabilités évolue, la classe doit être modifiée.

Solution : On divise les responsabilités !

Maîtriser les principes SOLID : pour un code propre et performant

Maintenant, chaque classe a une responsabilité distincte : UserValidator pour la validation, EmailService pour l'envoi d'emails et UserService pour la logique de gestion des utilisateurs. Ton code devient beaucoup plus facile à maintenir !


2. Le * O * ➡️ Open/Closed Principle

"Les entités doivent être ouvertes à l'extension, mais fermées à la modification."

Cela signifie que tu dois pouvoir ajouter de nouvelles fonctionnalités à une classe sans avoir à modifier son code existant.
Pourquoi ? Parce que chaque fois que tu modifies du code existant, tu risques d'introduire des bugs. Mieux vaut ajouter des fonctionnalités que de retoucher le code déjà écrit et testé.

Exemple avec Symfony

Imaginons que tu développes un service de paiement dans une boutique Symfony. Ce service gère différentes méthodes de paiement : carte de crédit, PayPal, etc. Si tu veux ajouter un nouveau mode de paiement (Bitcoin, par exemple), le principe Open/Closed te dit qu'il ne faut pas toucher au code déjà existant pour éviter les régressions.

Voici une mauvaise approche, où l'ajout de nouvelles fonctionnalités nécessite des modifications dans le code existant :

Maîtriser les principes SOLID : pour un code propre et performant

Le problème ici, c'est que chaque fois que tu veux ajouter un nouveau moyen de paiement, tu dois modifier la méthode processPayment, ce qui viole le principe Ouvert/Fermé.

Solution : Utiliser l'héritage et les interfaces

Pour résoudre ce problème et respecter le principe Ouvert/Fermé, tu vas utiliser une interface et des classes spécifiques pour chaque type de paiement. Comme ça, tu pourras ajouter de nouvelles méthodes de paiement sans toucher au code existant.

Maîtriser les principes SOLID : pour un code propre et performant
Maîtriser les principes SOLID : pour un code propre et performant
Maîtriser les principes SOLID : pour un code propre et performant

Quelques explications

  • Chaque méthode de paiement implémente l'interface PaymentMethodInterface, ce qui garantit qu'elles ont toutes la méthode pay().
  • Dans chaque classe, on utilise l'attribut #[AsTaggedItem('payment.method')] pour dire à Symfony que ces classes représentent des méthodes de paiement. Elles sont toutes taguées sous le même nom, payment.method, pour être facilement récupérables.

Le service principal PaymentService

Maintenant, notre PaymentService doit savoir quelle méthode de paiement utiliser, celle qui a été sélectionnée par l'utilisateur par exemple. Voici comment créer le PaymentService pour qu'il prenne en compte la méthode de paiement envoyée par le contrôleur.

Maîtriser les principes SOLID : pour un code propre et performant

Dans ton contrôleur, tu vas récupérer la méthode de paiement depuis la requête (via un query parameter par exemple) et la passer au PaymentService. Ton controller pourrait ressembler à ça :

Maîtriser les principes SOLID : pour un code propre et performant

Il est important que chaque méthode soit taguée correctement pour être injectée dans le PaymentService grâce au TaggedIterator.

En résumé, l'itérable $paymentMethods dans le PaymentService permet de récupérer dynamiquement toutes les méthodes de paiement disponibles, et le contrôleur passe cette information en fonction de la requête. Le service choisit ensuite la bonne méthode en fonction de la requête et procède au paiement.

Cette approche respecte le principe **open/closed tout en exploitant les fonctionnalités modernes de Symfony pour rendre le code plus flexible et extensible.


3. Le * L * ➡️ Liskov Substitution Principle

"Les objets d'une classe dérivée doivent pouvoir remplacer les objets de leur classe mère sans altérer le bon fonctionnement du programme."

Ce principe peut paraître un peu complexe à première vue, mais il est essentiel pour assurer la cohérence de ton code. En résumé, une classe fille doit pouvoir être utilisée partout où une classe mère est attendue, sans briser le comportement du programme.

Exemple avec Symfony

Prenons une classe Article et une classe FeaturedArticle, qui hérite de Article. Si FeaturedArticle ne respecte pas le contrat de la classe mère Article, tu risques d’avoir des problèmes lors de son utilisation dans certaines parties de ton code.

Voici un cas qui viole le principe de Liskov :

Maîtriser les principes SOLID : pour un code propre et performant

À première vue, ça peut sembler correct, mais si tu passes un FeaturedArticle dans une logique qui attend un Article, tu risques d’avoir des résultats inattendus.

Par exemple, imagine un système où les titres sont mis à jour automatiquement :

Maîtriser les principes SOLID : pour un code propre et performant

Le titre sera "Featured: Nouveautés", même si ce n'était pas prévu.

Solution

Pour respecter le principe de Liskov, tu dois faire en sorte que les classes dérivées n'altèrent pas le comportement attendu de la classe mère. Par exemple avec Symfony, si tu as un formulaire de base pour un Article, et que tu veux étendre son comportement pour un FeaturedArticle, tu peux étendre le FormType tout en conservant les propriétés de base.

1. Crée un FormType pour un article de base :

Maîtriser les principes SOLID : pour un code propre et performant

2. Crée un FormType pour un FeaturedArticle qui hérite de ArticleType :

Maîtriser les principes SOLID : pour un code propre et performant

Dans le contrôleur, utilise ce FormType pour un article à la une :

Maîtriser les principes SOLID : pour un code propre et performant

Avec cette approche, le principe de Liskov est respecté. Tu peux utiliser FeaturedArticleType partout où ArticleType est utilisé, tout en ajoutant des champs spécifiques sans casser le comportement de base.


4. Le * I * ➡️ Interface Segregation Principle

"Une classe ne doit pas être obligée d'implémenter des méthodes qu'elle n'utilisera jamais."

Si tu veux garder ton code flexible, ce principe est primordial. Il dit que si une classe implémente une interface, elle ne doit implémenter que ce dont elle a besoin. Si l'interface est trop générale et oblige une classe à implémenter des méthodes inutiles, elle deviendra plus complexe et difficile à maintenir.

Exemple avec Symfony

Imaginons que tu aies une interface pour un export de données, avec plusieurs types d’export (CSV, JSON, XML). Si tu crées une interface unique pour tout, tu ne respectes pas le principe de ségrégation des interfaces :

Maîtriser les principes SOLID : pour un code propre et performant

Si tu implémentes cette interface dans une classe qui n'a besoin que de JSON, tu te retrouves à devoir définir des méthodes inutiles, comme exportToCSV() et exportToXML().

Solution : Créer des interfaces spécifiques

La solution est de diviser cette interface en plusieurs petites interfaces qui sont spécifiques à chaque besoin :

Maîtriser les principes SOLID : pour un code propre et performant

Maintenant, chaque classe implémente seulement les interfaces dont elle a besoin :

Maîtriser les principes SOLID : pour un code propre et performant

Ton code est plus léger et maintenable, sans méthodes superflues !


5. Le * D * :arrow_rigt: Dependency Inversion Principle

"Les modules de haut niveau ne doivent pas dépendre des modules de bas niveau. Tous deux doivent dépendre d'abstractions."

? Dernier principe ! ?
Si tu es arrivé jusque là, je te félicite car ce n'était pas simple !

Alors, qu'est-ce qu'il nous dit ce dernier principe ?
Il nous dit tout simplement de ne pas dépendre de classes concrètes, mais plutôt d’abstractions (des interfaces ou des classes abstraites).

Exemple avec Symfony

Imaginons un service qui envoie des notifications. Une mauvaise approche serait de faire dépendre directement ce service des implémentations concrètes :

Maîtriser les principes SOLID : pour un code propre et performant

Ici, on voit que ce service dépend directement des implémentations concrètes d'email et de SMS (les classes EmailService et SmsService). Si tu veux changer le système d'envoi d'email, il faudra que tu modifies cette classe.

Solution

Une meilleure approche est de faire en sorte que NotificationService dépende d'une abstraction (une interface), plutôt que d'implémentations concrètes :

Interface pour les services de notification :
Maîtriser les principes SOLID : pour un code propre et performant

Implémentation pour les emails ? :
Maîtriser les principes SOLID : pour un code propre et performant

Implémentation pour les SMS ? :
Maîtriser les principes SOLID : pour un code propre et performant

Le service NotificationService dépend maintenant de NotificationInterface, ce qui te permet de changer facilement l'implémentation sans toucher au service principal.

Maintenant on va injecter dynamiquement ces services dans un NotificationManager :

Maîtriser les principes SOLID : pour un code propre et performant

Utilisation dans le contrôleur

Dans le contrôleur, on peut choisir dynamiquement le service de notification à utiliser :

Maîtriser les principes SOLID : pour un code propre et performant

  • On injecte toutes les implémentations de NotificationServiceInterface automatiquement grâce au tag #[AsTaggedItem('notification.service')] sur chaque service.
  • Le contrôleur peut choisir dynamiquement quel service utiliser via une query string dans la requête.

Cette solution suit parfaitement le dernier principe SOLID car :

  • Le NotificationManager ne dépend que de l'interface NotificationServiceInterface, et non des implémentations spécifiques.
  • Le code est ouvert à l'ajout de nouveaux services (par exemple, une notification par push) sans modifier le NotificationManager.

Conclusion

Et voilà ! Tu as maintenant une solide ? compréhension des principes SOLID et de leur application dans un projet Symfony. Le but de ces principes est de rendre ton code plus flexible, facile à maintenir, et surtout testable.

Ces concepts peuvent parfois sembler abstraits, mais lorsqu'ils sont appliqués, ils t'offrent un énorme gain en termes de qualité et de durabilité du code !

版本声明 本文转载于:https://dev.to/technivek/maitriser-les-principes-solid-pour-un-code-propre-et-performant-4a4m?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 如何避免Go语言切片时的内存泄漏?
    如何避免Go语言切片时的内存泄漏?
    ,a [j:] ...虽然通常有效,但如果使用指针,可能会导致内存泄漏。这是因为原始的备份阵列保持完整,这意味着新切片外部指针引用的任何对象仍然可能占据内存。 copy(a [i:] 对于k,n:= len(a)-j i,len(a); k
    编程 发布于2025-07-17
  • 人脸检测失败原因及解决方案:Error -215
    人脸检测失败原因及解决方案:Error -215
    错误处理:解决“ error:((-215)!empty()in Function Multultiscale中的“ openCV 要解决此问题,必须确保提供给HAAR CASCADE XML文件的路径有效。在提供的代码片段中,级联分类器装有硬编码路径,这可能对您的系统不准确。相反,OPENCV提...
    编程 发布于2025-07-17
  • PHP与C++函数重载处理的区别
    PHP与C++函数重载处理的区别
    作为经验丰富的C开发人员脱离谜题,您可能会遇到功能超载的概念。这个概念虽然在C中普遍,但在PHP中构成了独特的挑战。让我们深入研究PHP功能过载的复杂性,并探索其提供的可能性。在PHP中理解php的方法在PHP中,函数超载的概念(如C等语言)不存在。函数签名仅由其名称定义,而与他们的参数列表无关。...
    编程 发布于2025-07-17
  • 如何使用Java.net.urlConnection和Multipart/form-data编码使用其他参数上传文件?
    如何使用Java.net.urlConnection和Multipart/form-data编码使用其他参数上传文件?
    使用http request 上传文件上传到http server,同时也提交其他参数,java.net.net.urlconnection and Multipart/form-data Encoding是普遍的。 Here's a breakdown of the process:Mu...
    编程 发布于2025-07-17
  • 如何干净地删除匿名JavaScript事件处理程序?
    如何干净地删除匿名JavaScript事件处理程序?
    删除匿名事件侦听器将匿名事件侦听器添加到元素中会提供灵活性和简单性,但是当要删除它们时,可以构成挑战,而无需替换元素本身就可以替换一个问题。 element? element.addeventlistener(event,function(){/在这里工作/},false); 要解决此问题,请考虑...
    编程 发布于2025-07-17
  • CSS强类型语言解析
    CSS强类型语言解析
    您可以通过其强度或弱输入的方式对编程语言进行分类的方式之一。在这里,“键入”意味着是否在编译时已知变量。一个例子是一个场景,将整数(1)添加到包含整数(“ 1”)的字符串: result = 1 "1";包含整数的字符串可能是由带有许多运动部件的复杂逻辑套件无意间生成的。它也可以是故意从单个真理...
    编程 发布于2025-07-17
  • 在Pandas中如何将年份和季度列合并为一个周期列?
    在Pandas中如何将年份和季度列合并为一个周期列?
    pandas data frame thing commans date lay neal and pree pree'和pree pree pree”,季度 2000 q2 这个目标是通过组合“年度”和“季度”列来创建一个新列,以获取以下结果: [python中的concate...
    编程 发布于2025-07-17
  • FastAPI自定义404页面创建指南
    FastAPI自定义404页面创建指南
    response = await call_next(request) if response.status_code == 404: return RedirectResponse("https://fastapi.tiangolo.com") else: ...
    编程 发布于2025-07-17
  • 如何从Google API中检索最新的jQuery库?
    如何从Google API中检索最新的jQuery库?
    从Google APIS 问题中提供的jQuery URL是版本1.2.6。对于检索最新版本,以前有一种使用特定版本编号的替代方法,它是使用以下语法:获取最新版本:未压缩)While these legacy URLs still remain in use, it is recommended ...
    编程 发布于2025-07-17
  • 左连接为何在右表WHERE子句过滤时像内连接?
    左连接为何在右表WHERE子句过滤时像内连接?
    左JOIN CONUNDRUM:WITCHING小时在数据库Wizard的领域中变成内在的加入很有趣,当将c.foobar条件放置在上面的Where子句中时,据说左联接似乎会转换为内部连接。仅当满足A.Foo和C.Foobar标准时,才会返回结果。为什么要变形?关键在于其中的子句。当左联接的右侧值...
    编程 发布于2025-07-17
  • Java的Map.Entry和SimpleEntry如何简化键值对管理?
    Java的Map.Entry和SimpleEntry如何简化键值对管理?
    A Comprehensive Collection for Value Pairs: Introducing Java's Map.Entry and SimpleEntryIn Java, when defining a collection where each element com...
    编程 发布于2025-07-17
  • C++20 Consteval函数中模板参数能否依赖于函数参数?
    C++20 Consteval函数中模板参数能否依赖于函数参数?
    [ consteval函数和模板参数依赖于函数参数在C 17中,模板参数不能依赖一个函数参数,因为编译器仍然需要对非contexexpr futcoriations contim at contexpr function进行评估。 compile time。 C 20引入恒定函数,必须在编译时进行...
    编程 发布于2025-07-17
  • 如何使用PHP从XML文件中有效地检索属性值?
    如何使用PHP从XML文件中有效地检索属性值?
    从php $xml = simplexml_load_file($file); foreach ($xml->Var[0]->attributes() as $attributeName => $attributeValue) { echo $attributeName,...
    编程 发布于2025-07-17
  • 在Java中使用for-to-loop和迭代器进行收集遍历之间是否存在性能差异?
    在Java中使用for-to-loop和迭代器进行收集遍历之间是否存在性能差异?
    For Each Loop vs. Iterator: Efficiency in Collection TraversalIntroductionWhen traversing a collection in Java, the choice arises between using a for-...
    编程 发布于2025-07-17
  • Python中嵌套函数与闭包的区别是什么
    Python中嵌套函数与闭包的区别是什么
    嵌套函数与python 在python中的嵌套函数不被考虑闭合,因为它们不符合以下要求:不访问局部范围scliables to incling scliables在封装范围外执行范围的局部范围。 make_printer(msg): DEF打印机(): 打印(味精) ...
    编程 发布于2025-07-17

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3