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

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

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

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]删除
最新教程 更多>
  • 如何为PostgreSQL中的每个唯一标识符有效地检索最后一行?
    如何为PostgreSQL中的每个唯一标识符有效地检索最后一行?
    postgresql:为每个唯一标识符在postgresql中提取最后一行,您可能需要遇到与数据集合中每个不同标识的信息相关的信息。考虑以下数据:[ 1 2014-02-01 kjkj 在数据集中的每个唯一ID中检索最后一行的信息,您可以在操作员上使用Postgres的有效效率: id dat...
    编程 发布于2025-05-05
  • 大批
    大批
    [2 数组是对象,因此它们在JS中也具有方法。 切片(开始):在新数组中提取部分数组,而无需突变原始数组。 令ARR = ['a','b','c','d','e']; // USECASE:提取直到索引作...
    编程 发布于2025-05-05
  • 如何限制动态大小的父元素中元素的滚动范围?
    如何限制动态大小的父元素中元素的滚动范围?
    在交互式接口中实现垂直滚动元素的CSS高度限制,控制元素的滚动行为对于确保用户体验和可访问性是必不可少的。一种这样的方案涉及限制动态大小的父元素中元素的滚动范围。问题:考虑一个布局,其中我们具有与用户垂直滚动一起移动的可滚动地图div,同时与固定的固定sidebar保持一致。但是,地图的滚动无限期...
    编程 发布于2025-05-05
  • 如何使用Depimal.parse()中的指数表示法中的数字?
    如何使用Depimal.parse()中的指数表示法中的数字?
    在尝试使用Decimal.parse(“ 1.2345e-02”中的指数符号表示法表示的字符串时,您可能会遇到错误。这是因为默认解析方法无法识别指数符号。 成功解析这样的字符串,您需要明确指定它代表浮点数。您可以使用numbersTyles.Float样式进行此操作,如下所示:[&& && && ...
    编程 发布于2025-05-05
  • 哪种在JavaScript中声明多个变量的方法更可维护?
    哪种在JavaScript中声明多个变量的方法更可维护?
    在JavaScript中声明多个变量:探索两个方法在JavaScript中,开发人员经常遇到需要声明多个变量的需要。对此的两种常见方法是:在单独的行上声明每个变量: 当涉及性能时,这两种方法本质上都是等效的。但是,可维护性可能会有所不同。 第一个方法被认为更易于维护。每个声明都是其自己的语句,使其...
    编程 发布于2025-05-05
  • 如何在JavaScript对象中动态设置键?
    如何在JavaScript对象中动态设置键?
    在尝试为JavaScript对象创建动态键时,如何使用此Syntax jsObj['key' i] = 'example' 1;不工作。正确的方法采用方括号: jsobj ['key''i] ='example'1; 在JavaScript中,数组是一...
    编程 发布于2025-05-05
  • 如何使用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-05-05
  • eval()vs. ast.literal_eval():对于用户输入,哪个Python函数更安全?
    eval()vs. ast.literal_eval():对于用户输入,哪个Python函数更安全?
    称量()和ast.literal_eval()中的Python Security 在使用用户输入时,必须优先确保安全性。强大的Python功能Eval()通常是作为潜在解决方案而出现的,但担心其潜在风险。本文深入研究了eval()和ast.literal_eval()之间的差异,突出显示其安全性含义...
    编程 发布于2025-05-05
  • 如何将PANDAS DataFrame列转换为DateTime格式并按日期过滤?
    如何将PANDAS DataFrame列转换为DateTime格式并按日期过滤?
    Transform Pandas DataFrame Column to DateTime FormatScenario:Data within a Pandas DataFrame often exists in various formats, including strings.使用时间数据时...
    编程 发布于2025-05-05
  • 切换到MySQLi后CodeIgniter连接MySQL数据库失败原因
    切换到MySQLi后CodeIgniter连接MySQL数据库失败原因
    无法连接到mySQL数据库:故障排除错误消息要调试问题,建议将以下代码添加到文件的末尾.//config/database.php并查看输出: ... ... 回声'... echo '<pre>'; print_r($db['default']); echo '</pr...
    编程 发布于2025-05-05
  • Java中如何使用观察者模式实现自定义事件?
    Java中如何使用观察者模式实现自定义事件?
    在Java 中创建自定义事件的自定义事件在许多编程场景中都是无关紧要的,使组件能够基于特定的触发器相互通信。本文旨在解决以下内容:问题语句我们如何在Java中实现自定义事件以促进基于特定事件的对象之间的交互,定义了管理订阅者的类界面。以下代码片段演示了如何使用观察者模式创建自定义事件: args)...
    编程 发布于2025-05-05
  • Java是否允许多种返回类型:仔细研究通用方法?
    Java是否允许多种返回类型:仔细研究通用方法?
    在Java中的多个返回类型:一种误解类型:在Java编程中揭示,在Java编程中,Peculiar方法签名可能会出现,可能会出现,使开发人员陷入困境,使开发人员陷入困境。 getResult(string s); ,其中foo是自定义类。该方法声明似乎拥有两种返回类型:列表和E。但这确实是如此吗...
    编程 发布于2025-05-05
  • 为什么我会收到MySQL错误#1089:错误的前缀密钥?
    为什么我会收到MySQL错误#1089:错误的前缀密钥?
    mySQL错误#1089:错误的前缀键错误descript [#1089-不正确的前缀键在尝试在表中创建一个prefix键时会出现。前缀键旨在索引字符串列的特定前缀长度长度,以便更快地搜索这些前缀。理解prefix keys `这将在整个Movie_ID列上创建标准主键。主密钥对于唯一识别...
    编程 发布于2025-05-05
  • 如何有效地选择熊猫数据框中的列?
    如何有效地选择熊猫数据框中的列?
    在处理数据操作任务时,在Pandas DataFrames 中选择列时,选择特定列的必要条件是必要的。在Pandas中,选择列的各种选项。选项1:使用列名 如果已知列索引,请使用ILOC函数选择它们。请注意,python索引基于零。 df1 = df.iloc [:,0:2]#使用索引0和1 c...
    编程 发布于2025-05-05
  • 如何在Chrome中居中选择框文本?
    如何在Chrome中居中选择框文本?
    选择框的文本对齐:局部chrome-inly-ly-ly-lyly solument 您可能希望将文本中心集中在选择框中,以获取优化的原因或提高可访问性。但是,在CSS中的选择元素中手动添加一个文本 - 对属性可能无法正常工作。初始尝试 state)</option> < op...
    编程 发布于2025-05-05

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

Copyright© 2022 湘ICP备2022001581号-3