Fabrication industrielle
Internet des objets industriel | Matériaux industriels | Entretien et réparation d'équipement | Programmation industrielle |
home  MfgRobots >> Fabrication industrielle >  >> Industrial Internet of Things >> Cloud computing

SOLID : Maîtriser la conception orientée objet pour un code propre et maintenable

SOLID est un acronyme mnémotechnique pour la conception de classes en programmation orientée objet. Les principes instaurent des pratiques qui aident à développer de bonnes habitudes de programmation et un code maintenable.

En tenant compte de la maintenance et de l'extensibilité du code sur le long terme, les principes SOLID enrichissent l'environnement de développement de code Agile. La prise en compte et l'optimisation des dépendances de code aident à créer un cycle de vie de développement logiciel plus simple et organisé.

Quels sont les principes SOLID ?

SOLID représente un ensemble de principes pour la conception de classes. Robert C. Martin (Oncle Bob) a introduit la plupart des principes de conception et inventé l'acronym.
SOLID signifie :

Les principes SOLID représentent une collection de bonnes pratiques pour la conception logicielle. Chaque idée représente un cadre de conception, menant à de meilleures habitudes de programmation, une conception de code améliorée et moins d'erreurs.

SOLID : 5 Principes Expliqués

La meilleure façon de comprendre comment fonctionnent les principes SOLID est à travers des exemples. Tous les principes sont complémentaires et s'appliquent à des cas d'utilisation individuels. L'ordre dans lequel les principes sont appliqués n'est pas important, et tous les principes ne sont pas applicables dans chaque situation.

Chaque section ci-dessous fournit un aperçu de chaque principe SOLID dans le langage de programmation Python. Les idées générales de SOLID s'appliquent à tout langage orienté objet, tel que PHP, Java ou C#. La généralisation des règles les rend applicables aux approches de programmation modernes, telles que les microservices.

Principe de Responsabilité Unique (SRP)

Le principe de responsabilité unique (SRP) stipule : « Il ne devrait jamais y avoir plus d'une raison pour qu'une classe change. »

Lorsqu'on modifie une classe, on ne devrait changer qu'une seule fonctionnalité, ce qui implique que chaque objet ne devrait avoir qu'un seul travail.

Comme exemple, regardez la classe suivante :

# Une classe avec plusieurs responsabilités class Animal: # Constructeur de propriété def __init__(self, name): self.name = name # Représentation de propriété def __repr__(self): return f'Animal(name="{self.name}")' # Gestion de base de données def save(animal): print(f'Saved {animal} to the database') if __name__ == '__main__': # Instanciation de propriété a = Animal('Cat') # Sauvegarde de la propriété dans une base de données Animal.save(a) 

Lorsqu'on fait des changements à la méthode save(), le changement se produit dans la classe Animal. Lorsqu'on fait des changements de propriété, les modifications se produisent également dans la classe Animal.

La classe a deux raisons de changer et viole le principe de responsabilité unique. Même si le code fonctionne comme prévu, ne pas respecter le principe de conception rend le code plus difficile à gérer sur le long terme.

Pour implémenter le principe de responsabilité unique, remarquez que la classe exemple a deux tâches distinctes :

Par conséquent, la meilleure façon de résoudre le problème est de séparer la méthode de gestion de base de données dans une nouvelle classe. Par exemple :

# Une classe responsable de la gestion des propriétés class Animal: def __init__(self, name): self.name = name def __repr__(self): return f'Animal(name="{self.name}")' # Une classe responsable de la gestion de base de données class AnimalDB: def save(self, animal): print(f'Saved {animal} to the database') if __name__ == '__main__': # Instanciation de propriété a = Animal('Cat') # Instanciation de base de données db = AnimalDB() # Sauvegarde de la propriété dans une base de données db.save(a) 

Changer la classe AnimalDB n'affecte pas la classe Animal avec le principe de responsabilité unique appliqué. Le code est intuitif et facile à modifier.

Principe Ouvert/Fermé (OCP)

Le principe ouvert/fermé (OCP) stipule : « Les entités logicielles devraient être ouvertes pour l'extension mais fermées pour la modification. »

L'ajout de fonctionnalités et de cas d'utilisation au système ne devrait pas nécessiter la modification d'entités existantes. La formulation semble contradictoire — ajouter de nouvelles fonctionnalités nécessite de changer le code existant.

L'idée est simple à comprendre à travers l'exemple suivant :

class Animal: def __init__(self, name): self.name = name def __repr__(self): return f'Animal(name="{self.name}")' class Storage: def save_to_db(self, animal): print(f'Saved {animal} to the database') 

La classe Storage sauvegarde les informations d'une instance Animal dans une base de données. L'ajout de nouvelles fonctionnalités, telles que la sauvegarde dans un fichier CSV, nécessite l'ajout de code à la classe Storage :

class Animal: def __init__(self, name): self.name = name def __repr__(self): return f'Animal(name="{self.name}")' class Storage: def save_to_db(self, animal): print(f'Saved {animal} to the database') def save_to_csv(self,animal): printf(f'Saved {animal} to the CSV file') 

La méthode save_to_csv modifie une classe Storage existante pour ajouter la fonctionnalité. Cette approche viole le principe ouvert/fermé en changeant un élément existant lorsqu'une nouvelle fonctionnalité apparaît.

Le code nécessite de supprimer la classe Storage à usage général et de créer des classes individuelles pour le stockage dans des formats de fichiers spécifiques.

Le code suivant démontre l'application du principe ouvert/fermé :

class DB(): def save(self, animal): print(f'Saved {animal} to the database') class CSV(): def save(self, animal): print(f'Saved {animal} to a CSV file') 

Le code est conforme au principe ouvert/fermé. Le code complet ressemble maintenant à ceci :

class Animal: def __init__(self, name): self.name = name def __repr__(self): return f'"{self.name}"' class DB(): def save(self, animal): print(f'Saved {animal} to the database') class CSV(): def save(self, animal): print(f'Saved {animal} to a CSV file') if __name__ == '__main__': a = Animal('Cat') db = DB() csv = CSV() db.save(a) csv.save(a) 

L'extension avec des fonctionnalités supplémentaires (telles que la sauvegarde dans un fichier XML) ne modifie pas les classes existantes.

Principe de Substitution de Liskov (LSP)

Le principe de substitution de Liskov (LSP) stipule : « Les fonctions qui utilisent des pointeurs ou des références aux classes de base doivent être capables d'utiliser des objets de classes dérivées sans le savoir. »

Le principe stipule qu'une classe parente peut remplacer une classe enfant sans aucun changement notable dans la fonctionnalité.

Consultez l'exemple d'écriture de fichier ci-dessous :

# Classe parente class FileHandling(): def write_db(self): return f'Handling DB' def write_csv(self): return f'Handling CSV' # Classes enfants class WriteDB(FileHandling): def write_db(self): return f'Writing to a DB' def write_csv(self): return f"Error: Can't write to CSV, wrong file type." class WriteCSV(FileHandling): def write_csv(self): return f'Writing to a CSV file' def write_db(self): return f"Error: Can't write to DB, wrong file type." if __name__ == "__main__": # Instanciation classe parente et appels de fonctions db = FileHandling() csv = FileHandling() print(db.write_db()) print(db.write_csv()) # Instanciations classes enfants et appels de fonctions db = WriteDB() csv = WriteCSV() print(db.write_db()) print(db.write_csv()) print(csv.write_db()) print(csv.write_csv()) 

La classe parente (FileHandling) consiste en deux méthodes pour écrire dans une base de données et un fichier CSV. La classe gère les deux fonctions et retourne un message.

Les deux classes enfants (WriteDB et WriteCSV) héritent les propriétés de la classe parente (FileHandling). Cependant, les deux enfants lancent une erreur lorsqu'ils tentent d'utiliser la fonction d'écriture inappropriée, ce qui viole le principe de substitution de Liskov puisque les fonctions de remplacement ne correspondent pas aux fonctions parentes.

Le code suivant résout les problèmes :

# Classe parente class FileHandling(): def write(self): return f'Handling file' # Classes enfants class WriteDB(FileHandling): def write(self): return f'Writing to a DB' class WriteCSV(FileHandling): def write(self): return f'Writing to a CSV file' if __name__ == "__main__": # Instanciation classe parente et appels de fonctions db = FileHandling() csv = FileHandling() print(db.write()) print(csv.write()) # Instanciations classes enfants et appels de fonctions db = WriteDB() csv = WriteCSV() print(db.write()) print(csv.write()) 

Les classes enfants correspondent correctement à la fonction parente.

Principe de Ségrégation d'Interface (ISP)

Le principe de ségrégation d'interface (ISP) stipule : « De nombreuses interfaces spécifiques aux clients sont meilleures qu'une interface à usage général. »

En d'autres termes, les interfaces d'interaction plus étendues sont divisées en plus petites. Le principe garantit que les classes n'utilisent que les méthodes dont elles ont besoin, réduisant la redondance globale.

L'exemple suivant démontre une interface à usage général :

class Animal(): def walk(self): pass def swim(self): pass class Cat(Animal): def walk(self): print("Struts") def fly(self): raise Exception("Cats don't swim") class Duck(Animal): def walk(self): print("Waddles") def swim(self): print("Floats") 

Les classes enfants héritent de la classe parente Animal, qui contient les méthodes walk et fly. Bien que les deux fonctions soient acceptables pour certains animaux, certains animaux ont des fonctionnalités redondantes.

Pour gérer la situation, divisez l'interface en sections plus petites. Par exemple :

class Walk(): def walk(self): pass class Swim(Walk): def swim(self): pass class Cat(Walk): def walk(self): print("Struts") class Duck(Swim): def walk(self): print("Waddles") def swim(self): print("Floats") 

La classe Fly hérite de Walk, fournissant une fonctionnalité supplémentaire aux classes enfants appropriées. L'exemple satisfait le principe de ségrégation d'interface.
L'ajout d'un autre animal, tel qu'un poisson, nécessite d'atomiser davantage l'interface puisque les poissons ne peuvent pas marcher.

Principe d'Inversion de Dépendance (DIP)

Le principe d'inversion de dépendance stipule : « Dépendez des abstractions, pas des concretions. »

Le principe vise à réduire les connexions entre les classes en ajoutant une couche d'abstraction. Déplacer les dépendances vers des abstractions rend le code robuste.

L'exemple suivant démontre la dépendance de classe sans couche d'abstraction :

class LatinConverter: def latin(self, name): print(f'{name} = "Felis catus"') return "Felis catus" class Converter: def start(self): converter = LatinConverter() converter.latin('Cat') if __name__ == '__main__': converter = Converter() converter.start() 

L'exemple a deux classes :

Le principe d'inversion de dépendance nécessite l'ajout d'une couche d'interface d'abstraction entre les deux classes.

Un exemple de solution ressemble à ceci :

from abc import ABC class NameConverter(ABC): def convert(self,name): pass class LatinConverter(NameConverter): def convert(self, name): print('Converting using Latin API') print(f'{name} = "Felis catus"') return "Felis catus" class Converter: def __init__(self, converter: NameConverter): self.converter = converter def start(self): self.converter.convert('Cat') if __name__ == '__main__': latin = LatinConverter() converter = Converter(latin) converter.start() 

La classe Converter dépend maintenant de l'interface NameConverter au lieu de dépendre directement de LatinConverter. Les futures mises à jour permettent de définir des conversions de noms en utilisant une langue et une API différentes via l'interface NameConverter.

Pourquoi y a-t-il besoin des principes SOLID ?

Les principes SOLID aident à lutter contre les problèmes de patterns de conception. L'objectif global des principes SOLID est de réduire les dépendances de code, et l'ajout d'une nouvelle fonctionnalité ou le changement d'une partie du code ne casse pas toute la construction.

En conséquence de l'application des principes SOLID à la conception orientée objet, le code devient plus facile à comprendre, gérer, maintenir et changer. Comme les règles sont mieux adaptées aux grands projets, l'application des principes SOLID augmente la vitesse et l'efficacité globales du cycle de vie de développement.

Les principes SOLID sont-ils encore pertinents ?

Bien que les principes SOLID aient plus de 20 ans, ils fournissent toujours une bonne base pour la conception d'architecture logicielle. SOLID fournit des principes de conception solides applicables aux programmes et environnements modernes, pas seulement à la programmation orientée objet.

Les principes SOLID s'appliquent dans les situations où le code est écrit et modifié par des personnes, organisé en modules, et contient des éléments internes ou externes.

Conclusion

Les principes SOLID aident à fournir un bon cadre et guide pour la conception d'architecture logicielle. Les exemples de ce guide montrent que même un langage à typage dynamique comme Python bénéficie de l'application des principes à la conception de code.

Ensuite, lisez sur les 9 principes DevOps qui aideront votre équipe à tirer le meilleur parti de DevOps.

Cloud computing

  1. Mot-clé Static C# : Maîtriser les variables, méthodes et classes statiques
  2. Comprendre les classes imbriquées C# : Définition, utilisation et héritage
  3. Maîtriser les templates de classe en C++ : Guide pratique
  4. Maîtriser les classes internes anonymes en Java : définition, syntaxe et exemples pratiques
  5. Maîtriser Java ObjectOutputStream : Sérialisation, méthodes et exemples pratiques
  6. Maîtriser les génériques Java : Construire du code réutilisable et type-safe
  7. Maîtriser les opérations sur les fichiers Java avec java.io – Création, lecture, écriture et suppression
  8. Maîtriser l'héritage en C# : créer du code réutilisable et maintenable
  9. Polymorphisme en C# : liaison statique vs dynamique expliquée