Maîtrisant les recettes pour le développement efficace d'un projet logiciel, j'ai essayé de trouver par moi-même les raisons qui rendent utile d'utiliser les principes de développement de l'architecture SOLID (article Comment ne pas comprendre les principes de développement de l'architecture SOLID ).
Une analyse de ces principes a permis de distinguer plusieurs modèles clés et éléments de base qui existent dans le développement. Ils nous ont permis de décrire, comprendre et implémenter SOLID en vrai travail avec un projet logiciel.
Il est devenu intéressant d'effectuer une analyse de l'applicabilité de ces concepts pour les paradigmes de programmation généralement acceptés, par exemple, pour la POO. Eh bien, si le résultat de ce travail vous sera utile.

Aujourd'hui, il existe de nombreuses approches pour la conception et la mise en œuvre ultérieure de projets logiciels. Les plus demandés pour travailler avec de grands projets logiciels sont: la programmation structurelle, la programmation fonctionnelle , la programmation orientée objet .
Pour moi, il est devenu intéressant d'analyser les causes de ces approches de conception. Et dans le processus d'analyse, la découverte inattendue a été le fait qu'ils sont tous implicitement basés sur la prémisse suivante:
, .
Développement de projets logiciels
Qu'est-ce qu'un projet sans besoin de développement? De tels projets sont rarement trouvés et se caractérisent principalement par un paiement à la pièce rapide sans aucune obligation ultérieure de la part du programmeur, par exemple:
- un petit projet qui peut être écrit avec une seule approche;
- Un projet sans code structurellement complexe, chargé d'un grand nombre de relations;
- produit logiciel sans avoir besoin de son support et du support utilisateur.
Dans de telles situations, les efforts du programmeur pour maintenir, par exemple, une approche orientée objet sont vains. Il m'arrive souvent de me retrouver dans une leçon aussi dénuée de sens lors du développement d'un utilitaire de console à usage unique, quand je réalise soudain que l'écriture du texte de 4e année dans ce projet m'a retardé de 15 minutes et ne m'a pas rapproché du résultat. Le plus triste est que toutes les classes qui étaient à peine écrites dans de tels projets sont oubliées et non réutilisées, c'est-à-dire qu'elles ne facilitent pas notre travail à l'avenir.
Dans toutes les autres situations, le programmeur, en minimisant son travail, doit développer un projet structurellement complexe, à savoir:
- Corrigez les erreurs en analysant le code et en trouvant les endroits où ces erreurs sont générées.
- Pour introduire de nouvelles fonctionnalités, tout en conservant la fonctionnalité de toutes les capacités précédemment disponibles. Pour ce faire, utilisez le code existant (écrit et testé) dans la mise en œuvre de ces nouvelles tâches.
- Fournir une assistance dans l'utilisation d'un produit logiciel.
- Effectuer une description et la coordination des fonctionnalités de toutes les versions du projet.
- Gardez tous les formats de données utilisés par le projet (même obsolètes) opérationnels.
- Et effectuez de nombreuses autres tâches qui apparaissent dans la confrontation avec les concurrents provoquée par les changements de frameworks ou la fin du support des OS obsolètes ...
Si vous cherchez des analogies avec le développement d'un projet logiciel, vous pouvez rappeler l'évolution d'une espèce biologique.
"". - . - .
Le travail du programmeur n'est pas facile, mais le programmeur a un "assistant". Cet assistant est caché quelque part profondément dans la structure de notre monde, dans lequel il y a deux caractéristiques:
- la possibilité d'écrire un algorithme utile et de l'utiliser pour de nombreuses tâches similaires,
- la présence d'un grand nombre de tâches similaires dans leur solution.
Cet algorithme, utile dans de nombreux domaines, sera appelé algorithme universel par souci de concision. Sa mise en œuvre pour un domaine d'application spécifique peut être appelée spécialisation, car le processus de raffinage de l'algorithme pour une utilisation dans un champ d'application étroit est similaire à la spécialisation évolutive des cellules dans un organisme vivant.
Évidemment, pour créer un algorithme, il est nécessaire d'identifier les fonctionnalités qui garantissent l'applicabilité de l'algorithme. Ces signes doivent être recherchés dans les données d'entrée et dans la description de la situation initiale (contexte). Pour créer un algorithme universel , il est nécessaire dans chaque domaine, qui a ses propres ensembles de signes de données et de situations, d'identifier les signes d'applicabilité qui sont identiques pour tous les domaines. Tous les autres signes qui ne fournissent pas l'applicabilité sont ignorés par l'algorithme universel . En formalisant l'algorithme universel , nous sommes arrivés à la nécessité d'utiliser l' abstraction - l'un des principes les plus importants de la POO. De plus, la POO se caractérise par l'accent mis uniquement sur l'abstraction des données.
Ici, je vais essayer d'écrire des exemples d'utilisation de l' abstraction dans différents domaines.
Abstraction | Des algorithmes | Champ d'application |
---|
Nombres naturels | Algorithmes de calcul quantitatifs | Tâches de comptabilité des valeurs économiques |
Caractéristique de masse du corps du matériau | Algorithmes pour comparer la quantité de substance | Tâches de comparaison de la valeur d'un produit non responsable |
Interface avec les opérations pour une collection d'éléments: crawl complet, comparaison et échange de positions | Algorithmes de tri des collections | Programmation |
L'interface des mêmes opérations pour le "nœud d'extrémité" et le "nœud de branche" dans l'arborescence | Algorithmes basés sur le modèle de conception de disposition | Développement d'un projet logiciel complexe |
Concept clé "Employé" | Libellé dans la section "Contrat de travail" | Code du travail |
Bloc de construction d'un projet logiciel
À l'aide de diverses techniques d'abstraction, le programmeur implémente l'algorithme sous la forme d'une section de code, qui est un élément distinct et complet de son travail. Cet élément, selon le langage de programmation utilisé, peut être une fonction, un objet et une séquence d'instructions. Pour plus de commodité, nous appellerons ce fragment de code le mot « composant ».
Composant - un morceau de code (procédure, classe, composant de déploiement, etc.):
- qui implémente un algorithme complet qui fonctionne dans certaines situations initiales et avec certaines données d'entrée,
- qui peut être utilisé plusieurs fois dans un projet (encore mieux plusieurs fois dans différents projets),
- dont toutes les instructions se trouvent proches et consultées sans nécessiter d'opérations de recherche supplémentaires dans l'environnement de développement,
- changements dans lesquels le programmeur fonctionne de manière relativement indépendante par rapport au reste du code.
Patterns dans le développement d'un projet logiciel
En utilisant le terme composant , il devient possible de formuler un ensemble de lois simples qui existent dans le développement d'un projet logiciel. Je présenterai ces modèles sous la forme des déclarations suivantes, divisées en 3 catégories.
- Déclarations décrivant les propriétés d'un composant .
1.1. Un composant correctement écrit est nécessairement utilisé et le plus souvent plusieurs fois.
1.2. Dans chaque endroit où le composant est utilisé, un comportement constant est attendu de lui, conduisant à un résultat reproductible.
1.3. Lors de l'utilisation du composant à plusieurs endroits, le résultat doit satisfaire chaque lieu d'utilisation.
1.4. Le comportement incorporé dans le composant crée des restrictions sur les lieux d'utilisation de ce composant .
1.5. Dans chaque lieu d'utilisation du composant , toutes ses restrictions peuvent être impliquées.
1.6. Toute modification apportée à un composant modifie ses limites et nécessite une vérification de tous les lieux d'utilisation, ce qui fait perdre du temps à un programmeur.
1.7. Il est conseillé d'écrire le composant sous forme de code dans une seule instance, c'est-à-dire qu'il est nécessaire d'éliminer la duplication du même code. Cela réduira le nombre de modifications lors de la modification d'un composant . - Déclarations décrivant des modèles dans la mise en œuvre d'une nouvelle tâche par le programmeur.
2.1 Il est conseillé de choisir une option pour implémenter une nouvelle tâche tout en minimisant le temps passé par le programmeur.
2.2. Pour implémenter une nouvelle tâche, un programmeur peut ajouter de nouveaux composants ou modifier le comportement des anciens composants .
2.3. L'ajout d'un composant nécessite essentiellement une vérification uniquement sur le lieu de la nouvelle utilisation et génère un temps minimal pour le programmeur.
2.4. Le changement de comportement d'un composant provoqué par la nouvelle tâche, selon la déclaration [1.6], nécessite une vérification sur le lieu de la nouvelle utilisation et dans tous les lieux de l'ancienne utilisation, ce qui génère du temps supplémentaire pour le programmeur par rapport à la situation de la déclaration [2.3]. Dans le cas d'un composant publié , cela nécessite le travail de tous les programmeurs utilisant le composant modifié. - Déclarations décrivant les modèles d'interaction des algorithmes universels et de leurs spécialisations:
3.1. Il est possible d'écrire un composant de base (le nom est introduit par analogie avec la classe de base et par souci de concision, nous utiliserons le mot " base "). La base ne remplit que les caractéristiques les plus importantes de certains algorithmes universels .
3.2. Il est possible d'écrire un composant - spécialisation (ci-après, par souci de concision, nous utiliserons le mot " spécialisation "). La spécialisation complète l'algorithme universel de la base , le rendant applicable dans un domaine d'utilisation particulier.
3.3. La base , comme suit des déclarations [3.1], [3.2], a moins de complexité et moins de restrictions d'application que la spécialisation .
3.4. Selon la déclaration [1.7], il est conseillé de développer une spécialisation sans duplication du code de l'algorithme universel de la base de données .
3.5. Les lieux d'utilisation de la base de données ne nécessitent pas de vérification après avoir apporté des modifications à la spécialisation correctement formée.
Concepts de programmation orientée objet
J'essaierai, en utilisant les déclarations ci-dessus, d'analyser les concepts de base de la programmation orientée objet. Cette analyse contourne le concept d' abstraction , car il a déjà été décrit précédemment dans la formalisation de la méthode de construction d'un algorithme universel .
Classe, objet
Ces concepts de POO renforcent la possibilité d'utiliser un type spécial de composant décrit par une combinaison de certaines données internes et de méthodes pour travailler avec ces données. Toutes les instructions du groupe [1] et [2] sont traduites en POO, pour lequel le terme composant est remplacé par le concept de classe .
Dans le même temps, à première vue, les relations entre une classe et un objet sont épuisées par un groupe d'instructions [3], dans lequel la base est remplacée par le concept d'une classe , et l' implémentation est remplacée par le concept d' un objet . De plus, l' implémentation est dynamique, c'est-à-dire modifiable lors de l'exécution du programme.
Encapsulation
Le concept d '" encapsulation " peut être envisagé de deux "côtés".
Le premier côté du concept d '" encapsulation " est l'isolement du composant des autres parties du code. Cette propriété permet au programmeur d'effectuer des opérations dans des zones du code situées "à proximité" pour apporter des modifications au composant . C'est-à-dire, pour minimiser le temps passé par le programmeur en excluant du travail la recherche et l'analyse des éléments interactifs disparates du programme. Ce côté est défini par les propriétés du composant suite à sa définition.
Le deuxième côté du concept d '" encapsulation " est la dissimulation de l'implémentation interne du composant . Cette dissimulation est possible en utilisant les concepts de base et d' implémentation décrits dans le groupe d'instructions [3]. Pour ce faire, les méthodes de classe publique sont identifiées avec la base , et les méthodes de classe privées et protégées sont identifiées avec l' implémentation . Dans les lieux d'utilisation, les restrictions formées par la base sont utilisées , et il devient donc possible d'apporter des modifications dans l' implémentation qui ne sont pas liées aux restrictions de base . Et ces changements d' implémentation n'ont pas besoin d'être vérifiés aux endroits où la base de données est utilisée [3.5], ce qui minimise le travail du programmeur.
Il est à noter que le concept d '" encapsulation " a une analogie en biologie. Ce premier processus est similaire aux fonctions biologiques de la " membrane cellulaire ".
Héritage
Le concept d '" héritage " continue de renforcer l'importance d'utiliser une combinaison de base + implémentation . Pour cela, dans le groupe d'instructions [3] il est nécessaire d'identifier les méthodes de la classe parente avec la base , et d'identifier les méthodes de la classe successeur avec l' implémentation .
Dans sa mise en œuvre, le concept d '« héritage » permet d'utiliser l'instruction [2.3], c'est-à-dire d'utiliser l'ajout de code au lieu de le modifier et de le dupliquer. Dans ce cas, il faut exclure la duplication de l'algorithme de base . Cependant, une approche qui utilise l' héritage pour spécialiser un algorithme universel a un inconvénient significatif. Cet inconvénient est la présence de deux composants fortement connectés, qui sont difficiles à changer indépendamment. Ces relations de dépendance sont générées par la relation parent-enfant.
Il existe de nombreuses autres façons d'utiliser le bundle d' implémentation base +. Je donnerai d'autres exemples de telles méthodes.
Base | Implémentation | Champ d'application |
---|
Méthodes de classe publique | Méthodes de cours privés | Encapsulation |
Méthodes protégées de la classe parente | Méthodes de classe d'héritage | Héritage |
Interface de bibliothèque dynamique | Fonctionnalité de bibliothèque dynamique | Composant = bibliothèque dynamique |
Méthodes et classes de modèle (généralisées) (modèle, générique) | Instanciation d'un modèle avec des arguments spécifiés | Programmation générale |
Méthodes génériques acceptant les délégués | Spécialisation des méthodes indiquant des procédures de traitement spécifiques | Procédures de tri ou de formation d'un arbre, indiquant la méthode d'évaluation de l'ordre des éléments |
Classes qui permettent l'interaction avec le modèle Visitor | Formation de "Visiteur" avec la fonctionnalité requise | Modèle de conception de visiteur |
Panneau de commande NPP | L'ensemble de l'automatisation et de l'équipement des centrales nucléaires | Dissimulation de la complexité du système par l'opérateur de la centrale nucléaire |
En même temps, je remarque que pour le concept d '" héritage " de l'OLP, on peut aussi trouver une analogie dans les processus d'évolution biologique. En biologie, le terme " hérédité " est utilisé pour cela.
Polymorphisme
À mon avis, le concept de " polymorphisme " est le deuxième côté lorsque l'on examine la procédure de création d'un algorithme universel . Le premier côté ( abstraction ) est une vue du point de vue de la façon de créer un algorithme universel . En même temps, lorsque nous regardons l'algorithme universel du point de vue de l'utilisateur, nous obtenons un enregistrement du concept de polymorphisme . Autrement dit, le polymorphisme est une capacité utile d'une fonction ( composant ) à traiter des données de différents types. L'ajout de ce concept à la POO renforce l'utilité d'utiliser un algorithme universel dans le développement d'un projet logiciel.
Les implémentations de polymorphisme dans différents langages de programmation sont très différentes. L'article de Wikipedia sur le polymorphisme , en fonction de sa mise en œuvre, identifie 4 sous-types: paramétrique, inclusion (ou sous-types), surcharge, transtypage de type. Ces implémentations présentent des différences importantes, mais elles sont toutes unies par un seul objectif: il s'agit d'écrire un algorithme universel qui n'aura pas besoin d'être dupliqué pour sa spécialisation spécifique.
Et cette fois, presque sans surprise, il a trouvé une analogie avec le concept de " polymorphisme " en biologie. Le nom de ce terme biologique coïncide pleinement avec le concept de POO. " Polymorphisme " - la capacité d'un organisme à exister dans des états avec différentes structures internes ou sous différentes formes externes.
Conclusion
Ainsi, presque tous les concepts de base de la POO peuvent être représentés comme un ensemble de déclarations simples formées sur la base des lois de développement d'un projet logiciel. De plus, pour la POO, le terme composant est identifié au concept de classe . Si nous distinguons une signification différente pour le terme composant , par exemple, une fonction , alors il est possible de formuler les concepts de base de la programmation fonctionnelle .
Au cours de la rédaction de l'article, des analogies biologiques ont été trouvées pour les concepts utilisés en programmation. Ces analogies apparaissent en raison de la similitude des méthodes de développement d'un produit logiciel et de certains processus d'évolution biologique.
À mon humble avis, il convient de considérer ces deux domaines scientifiques ensemble. Dans ce cas, il peut être possible d'effectuer le transfert de lois d'une industrie à une autre et d'assurer ainsi le développement des technologies de l'information et des descriptions formelles des processus biologiques.
Merci de votre attention.
Les avis
Je serais très reconnaissant pour les commentaires, suggestions et suggestions, car ils m'aident à ajuster la direction du développement du travail dans ce domaine.
Les références
Sous la direction de Borisova M.V.