OOP, la "Sainte Trinité" et SOLIDE: quelques connaissances minimales à leur sujet

Entrée obligatoire


Je ne peux pas garantir que les interprétations des termes et principes généralement acceptés énoncés ici coïncident avec ce que les professeurs de Californie ont présenté dans des articles scientifiques solides dans la seconde moitié du siècle dernier. Je ne peux pas garantir que mes interprétations sont entièrement partagées ou partagées par la plupart des professionnels de l'informatique de l'industrie ou de la communauté universitaire. Je ne peux même pas garantir que mes interprétations vous aideront dans l’entretien, même si je suppose qu’elles seront utiles.


Mais je garantis que si le manque de compréhension est remplacé par mes interprétations et commence à les appliquer, alors le code que vous avez écrit sera plus facile à maintenir et à modifier. Je comprends aussi très bien que dans les commentaires, j'écrirai ce qui sera écrit avec fureur, ce qui me permettra de corriger des omissions et des incohérences absolument flagrantes.


Ces petites garanties soulèvent des questions sur les raisons pour lesquelles l'article est écrit. Je crois que ces choses devraient être enseignées partout où elles enseignent la programmation, jusqu'aux cours d'informatique dans les écoles avec une étude approfondie. Néanmoins, pour moi, c'est devenu une situation effroyablement normale lorsque je découvre que l'interlocuteur est mon collègue, et qu'il travaille depuis plusieurs années, mais à propos de l'encapsulation «j'ai entendu quelque chose là-bas». La nécessité de rassembler tout cela en un seul endroit et de donner un lien lorsque des questions se posent est mûre depuis longtemps. Et puis mon «projet pour animaux de compagnie» m'a donné matière à réflexion.


Ici, ils peuvent m'objecter qu'il est trop tôt pour apprendre ces choses à l'école, et en général, la lumière ne s'est pas réunie dans la POO. Tout d'abord, cela dépend de la façon dont vous apprenez. Deuxièmement, 70% du contenu de cet article s'applique non seulement à la POO. Ce que je noterai séparément.




OOP en bref


C'est probablement la section la plus difficile pour moi. Mais encore, vous devez établir la base et décrire très brièvement quelle est l'essence de la POO afin de comprendre pourquoi l'encapsulation, le polymorphisme, l'héritage et les principes SOLIDES l'ont renforcé. Et je vais le faire en parlant de la façon dont vous pourriez même penser à une telle chose.


Tout a commencé avec Dijkstra, qui a prouvé que tout algorithme peut être exprimé de trois manières pour sélectionner la commande suivante: exécution linéaire (dans l'ordre), ramification par condition, exécution en boucle pendant que la condition est satisfaite. En utilisant ces trois connexions, vous pouvez construire n'importe quel algorithme. De plus, il était recommandé d'écrire des programmes, limités à une disposition linéaire des commandes les unes après les autres, des branchements et des boucles. Cela a été appelé "programmation structurelle procédurale " (merci pour la clarification sshikov ).


Ici aussi, nous notons que la séquence de commandes doit être combinée en sous-programmes et chaque sous-programme peut être exécuté à l'aide d'une seule commande.


En plus de l'ordre des actions, il est important pour nous celui sur lequel les actions sont effectuées. Et ils sont effectués sur des données qui sont devenues habituelles à stocker dans des variables. Les variables stockent des données. Ils sont interprétés en fonction de leur type. Évidemment au grincement des dents, mais soyez patient un peu, s'il vous plaît.


Dès le début, un ensemble plus ou moins général de types de données primitifs s'est formé. Entiers, nombres réels, variables booléennes, tableaux, chaînes. Algorithmes + structures de données = programmes, comme l'a légué Nicklaus Wirth.


Aussi, depuis le tout début des temps, il existait sous différentes formes un type de données tel qu'un sous-programme. Ou un morceau de code si vous le souhaitez. Certains pourraient dire que l'utilisation de sous-programmes comme variables est la prérogative de la programmation fonctionnelle. Même ainsi, la possibilité de créer un morceau de code variable est même dans l'assembleur. Laissez cette possibilité se résumer à "eh bien, voici le nombre d'octets dans la RAM où se trouve ce sous-programme, puis la commande CALL avec la pile d'appels dans les dents et tournez comme vous le pouvez."


Bien sûr, plusieurs types de données étaient peu nombreux et les gens ont commencé à penser à ajouter la possibilité de créer leurs propres types à divers PL. Une variante de cette fonctionnalité était les soi-disant enregistrements. Vous trouverez ci-dessous deux exemples d'enregistrement dans un langage de programmation inexistant (ci-après NEPL - Langage de programmation inexistant):


type Name: record consisting of FirstName: String, MiddleName: String, LastName: String. type Point: record consisting of X: Double, Y: Double. 

Autrement dit, au lieu de faire glisser deux ou trois variables liées, vous les regroupez en une seule structure avec les champs nommés. Ensuite, vous déclarez une variable de type Nom et faites référence au champ FirstName, par exemple.


Qu'est-ce qui est si précieux dans cette variable «améliorée» pour notre sujet? Que d'ici il n'y a qu'un petit pas vers la POO. Je n'ai pas simplement mis en évidence un paragraphe en gras pour indiquer que des morceaux de code peuvent également être placés dans des variables. Voyez comment les variables se transforment en objets:


 type Name: class consisting of FirstName: String, MiddleName: String, LastName: String, GetFullName: subprogram with no parameters returns String. type Point: class consisting of X: Double, Y: Double, ScalarMultiply: subprogram with (Double) parameters returns Point. 

NB NEPL se développe activement et a déjà remplacé le mot-clé record par class.


Autrement dit, nous pouvons accéder au champ «GetFullName» et l' appeler . Une variable contient non seulement des données décrivant son état, mais aussi son comportement. Ainsi, la variable se transforme en un objet qui a des compétences et un état. Et nous travaillons déjà non seulement avec des variables, mais avec de petits systèmes qui peuvent recevoir des commandes.


Dans ma jeunesse, cette idée m'a fasciné. Pensez-y, vous pouvez créer n'importe quel type de données . Et vous ne travaillez pas avec des chiffres, mais avec les objets du monde que vous créez. Pas de tourment avec des tableaux ennuyeux ou des ensembles de nombres complexes. Nous travaillons directement avec des objets de types Player, Enemy, Bullet, Boss! Oui, dans ma jeunesse, je voulais faire des jeux vidéo.


En réalité, tout s'est avéré pas si simple. Et sans quelques idées de «renforcement», la POO transformera la vie d'un programmeur en enfer. Mais avant de poursuivre, donnons quelques termes supplémentaires:


  • Les types de données qui sont «renforcés» par leur comportement dans la POO sont appelés classes .
  • Les variables de ces types sont appelées objets .
  • Et les routines qui définissent le comportement des objets sont appelées méthodes . En règle générale, chaque classe possède son propre ensemble de méthodes, et non chaque objet. Pour que chaque objet d'une certaine classe se comporte comme les autres objets de la même classe. Je serai heureux de savoir des commentaires sur les langues où les choses sont différentes.

Sainte Trinité


Il est donc arrivé historiquement qu'ils posent des questions sur ces choses lors des entretiens. Ils sont écrits dans n'importe quel manuel de langue OOP. Pourquoi? Parce que si vous concevez un programme OOP sans aucun égard pour l'encapsulation et le polymorphisme, vous obtiendrez un "cercueil, cercueil, cimetière, non accompagné". L'héritage n'est pas si strictement nécessaire, mais ce concept vous permet de mieux comprendre la POO et est l'un des principaux outils de conception à l'aide de la POO.


Encapsulation


Eh bien, commençons par la définition de Wikipedia: "regrouper les données et les fonctions dans un seul composant". La définition semble claire, mais en même temps trop généralisée. Par conséquent, parlons de la raison pour laquelle cela est nécessaire, de ce qu'il nous apportera et de la façon exacte de regrouper les données et les fonctions dans un seul composant.


J'ai déjà écrit un article sur l'encapsulation. Et là, on m'a reproché, à juste titre, d'avoir réduit l'encapsulation à la dissimulation d'informations, et ce sont des choses quelque peu différentes. En particulier, EngineerSpock a produit une formulation aussi élégante que la protection invariante . J'avoue mon erreur, puis je vais expliquer pourquoi je l'ai fait.


En attendant, la mienne, une définition préliminaire du principe d'encapsulation, ou, si vous voulez, le processus d'encapsulation, qui décrit non seulement le principe d'encapsulation, mais aussi ce qu'il est censé réaliser avec lui:


Toute entité logicielle avec un état non trivial doit être transformée en un système fermé, qui ne peut être transféré que d'un état correct à un autre.


A propos de la partie où "toute entité logicielle qui a un état non trivial", voyons un peu plus tard. Pour l'instant, nous parlerons exclusivement des objets. Et à propos de la deuxième partie de ma définition. Pourquoi en avons-nous besoin?


Tout est simple ici: ce qui ne peut être transféré que d'un état correct à un autre ne peut pas être brisé. Autrement dit, nous devons nous assurer qu'aucun objet ne peut être brisé. Cela semble, pour le moins, ambitieux. Comment y parvenir?


Dans le zéro, tout ce qui concerne l'objet doit se trouver au même endroit, à l'intérieur de la même frontière architecturale, disons. Au cas où cela se révélerait très abstrus, je répéterai la définition de Wikipedia: "regrouper les données et les fonctions dans un seul composant".


Tout d'abord, pour séparer clairement l'interface et sa mise en œuvre. Je pense que tous mes collègues connaissent l' API d' abréviation. Ainsi, chaque objet doit avoir sa propre API, ou PI, si nous voulons être méticuleux. Ce pour quoi ils le créent, et ce que les autres utiliseront, ce qu'ils provoqueront. À quoi cela devrait-il ressembler? Pour que personne ne pense même à pénétrer à l'intérieur d'un objet et à l'utiliser de manière inappropriée. Mais pas plus que ça.


Dans un livre, hélas, je ne me souviens pas lequel, cela s'explique par l'exemple d'un micro-ondes. Il y a des boutons dessus. Stylos Ils vous permettent de réchauffer la nourriture. Vous n'avez pas besoin de dérouler le micro-ondes et de souder quelque chose pour réchauffer la soupe d'hier. Vous avez une interface, des boutons. Mettez une assiette, appuyez sur quelques boutons, attendez une minute et soyez heureux.


Pensez simplement aux boutons sur lesquels l'utilisateur de votre objet doit appuyer et séparez-les des abats internes. Et en aucun cas n'ajoutez de boutons supplémentaires! Ce fut le premier.


Deuxièmement, respectez la frontière entre l'interface et l'implémentation et faites en sorte que les autres la respectent. En principe, cette idée est intuitive et plane parmi la sagesse populaire sous de nombreuses formes. Prenez au moins "si vous avez profité de quelque chose sans papiers, et puis quelque chose s'est cassé pour vous, vous êtes à blâmer." Je pense qu'avec «ne faites pas tourner le micro-ondes jusqu'à ce qu'il fonctionne comme vous en avez besoin», tout est clair. Maintenant, comment inciter les autres à respecter la frontière notoire.


C'est là que la dissimulation même d'informations vient à la rescousse. Oui, vous pouvez toujours accepter, demander, mettre en place des conventions de code, signaler une révision du code que ce n'est pas possible. Mais la possibilité même de franchir cette frontière restera. C'est la dissimulation même des informations qui viennent à la rescousse.


Nous ne pouvons pas franchir la frontière notoire si notre code ne peut pas connaître son existence et ce qui se cache derrière. Et même s'il le découvre, le compilateur prétendra qu'il n'y a pas un tel champ ou une telle méthode, et même s'il y en a, alors il n'est pas nécessaire de le toucher, je refuse de compiler, et généralement qui vous êtes, nous ne vous avons pas appelé, utilisez la partie interface.



C'est là que toutes sortes de modificateurs d'accès publics, privés et autres entrent en jeu à partir de votre langue préférée. La «dissimulation d'informations» est le moyen le plus fiable de conserver les avantages de l'encapsulation dans les égouts. Quoi qu'il en soit, cela n'a aucun sens de regrouper tout ce qui concerne une classe en un seul endroit, si le code utilise ce qu'il veut et où il veut. Mais avec la dissimulation d'informations, une telle situation ne devrait plus se produire en principe. Et cette méthode est si fiable que dans l'esprit de milliers et de milliers de programmeurs (dont moi, qui est déjà là), la différence entre l'encapsulation et la dissimulation d'informations est en quelque sorte lissée.


Que faire si votre PJ préféré ne vous permet pas de masquer des informations? Sur ce sujet, vous pouvez vous amuser à parler de commentaires. Je vois la prochaine issue. Croissant:


  • Documentez uniquement la partie interface et considérez tout ce qui n'est pas documenté comme une implémentation.
  • Séparez l'implémentation de l'interface via la convention de code (exemple - en python, il y a la variable __all__ qui indique exactement ce qui sera importé du module lorsque vous demandez de tout importer).
  • Rendez ces conventions de code très strictes afin qu'elles puissent être vérifiées automatiquement, après quoi toute violation de celles-ci sera assimilée à une erreur de compilation et à une construction tombée.

Encore une fois:


  • Tout ce qui concerne une classe est regroupé dans un module.
  • Entre les classes, des limites architecturales strictes sont tracées.
  • Dans n'importe quelle classe, la partie interface est séparée de l'implémentation de cette partie interface elle-même.
  • Les frontières entre les classes doivent être respectées et obligées de respecter les autres!

Je terminerai par un exemple sur NEPL, qui est toujours en développement très actif et, après dix paragraphes, a acquis des modificateurs d'accès:


 type Microwave: class consisting of private fancyInnerChips: List of Chip, private foodWarmingThing: FoodWarmerController, private buttonsPanel: ButtonsPanel, public GetAccessToControlPanel: subprogram with no parameters returns ButtonsPanel, public OpenDoor: subprogram with no parameters returns nothing, public Put: subprogram with (Food) parameters return nothing, public CloseDoor: subprogram with no parameters returns nothing. type ButtonsPanel: class consisting of private buttons: List of ButtonState, public PressOn: subprogram with no parameters returns nothing, public PressOff: subprogram with no parameters returns nothing, public PressIncreaseTime: subprogram with no parameters returns nothing, public PressDecreaseTime: subprogram with no parameters returns nothing, public PressStart: subprogram with no parameters returns nothing, public PressStop: subprogram with no parameters returns nothing. 

J'espère que le code montre clairement l'essence de l'exemple. Je vais clarifier un seul point: GetAccessToControlPanel vérifie si nous pouvons même toucher le micro-ondes. Et si elle est brisée? Ensuite, vous ne pouvez rien cliquer. Vous ne pouvez obtenir qu'un message d'erreur.


Eh bien, le fait que ButtonsPanel est devenu une classe séparée en douceur nous amène à une question importante: quel est le «composant unique» de la définition de l'encapsulation sur Wikipedia? Où et comment devraient se situer les frontières entre les classes? Nous reviendrons certainement sur cette question un peu plus tard.

Spoiler
Principe de responsabilité unique

Utilisation en dehors de la POO


De nombreux programmeurs ont appris l'encapsulation à partir d'un didacticiel Java / C ++ / C # / remplacer votre premier langage OOP. Par conséquent, l'encapsulation dans la conscience de masse est en quelque sorte liée à la POO. Mais revenons aux deux définitions de l'encapsulation.


Emballage des données et des fonctions dans un seul composant.

Toute entité logicielle avec un état non trivial doit être transformée en un système fermé, qui ne peut être transféré que d'un état correct à un autre.


L'avez-vous remarqué? Il n'y a rien du tout sur les classes et les objets!


Donc un exemple. Vous êtes DBA . Votre travail consiste à garder un œil sur une sorte de base de données relationnelle. Que ce soit, par exemple, sur MySQL. Votre précieuse base de données utilise plusieurs programmes. Vous n'avez aucun contrôle sur certains d'entre eux. Que faire


Créez un ensemble de procédures stockées. Nous les composons en un seul circuit, que nous appellerons interface. Nous créons un utilisateur pour nos programmes sans aucun droit. Il s'agit de la commande CREATE USER. Ensuite, à l'aide de la commande GRANT, les utilisateurs ne disposent que du droit d'exécuter ces procédures stockées à partir du schéma d'interface.


C’est tout. Nous avons une base de données, l'entité très logicielle avec un état non trivial, qui est assez facile à casser. Et pour ne pas le casser, nous créons une interface à partir des procédures stockées. Et après les moyens de MySQL lui-même, nous faisons en sorte que les entités tierces puissent utiliser uniquement cette interface.


Notez l'encapsulation notoire, telle qu'elle est, et comment elle a été décrite, est utilisée à son plein potentiel. Mais il y a un tel écart entre la représentation relationnelle des données et des objets qu'il doit être fermé avec des cadres ORM volumineux.


C'est pourquoi les classes et les objets n'apparaissent pas dans la définition de l'encapsulation. L'idée est beaucoup plus large que la POO. Et cela apporte trop d'avantages de ne parler que dans des manuels sur les langues de la POO.


Polymorphisme


Le polymorphisme a de nombreuses formes et définitions. Il suffit que Kondratius me suffise lorsque j'ouvre Wikipédia. Ici, je vais parler du polymorphisme, tel que Straustrup l'a formulé: une interface - de nombreuses implémentations .


Sous cette forme, l'idée de polymorphisme peut considérablement renforcer la position des écrivains soucieux d'encapsulation. Après tout, si nous séparons l'interface de l'implémentation, celui qui utilise l'interface n'a pas besoin de savoir que quelque chose a changé dans l'implémentation. Ceux qui utilisent l'interface (idéalement) n'ont même pas besoin de savoir que l'implémentation a changé du tout! Et cela ouvre des possibilités infinies d'extension et de modification. Votre prédécesseur a-t-il décidé qu'il était préférable de réchauffer la nourriture avec un radar militaire? Si ce génie fou a séparé l'interface de la mise en œuvre et l'a formalisée clairement, alors le radar militaire peut être adapté à d'autres besoins, et son interface pour chauffer des aliments peut être réalisée à l'aide d'un micro-ondes.


NEPL se développe rapidement et, sous l'influence de C #, acquiert (soigneusement, ne butez pas sur le libellé) le type de données d'interface.


 type FoodWarmer: interface consisting of GetAccessToControlPanel: no parameters returns FoodWarmerControlPanel, OpenDoor: no parameters returns nothing, Put: have (Food) parameters returns nothing, CloseDoor: no parameters returns nothing. type FoodWarmerControlPanel: interface consisting of PressOn: no parameters returns nothing, PressOff: no parameters returns nothing, PressIncreaseTime: no parameters returns nothing, PressDecreaseTime: no parameters returns nothing, PressStart: no parameters returns nothing, PressStop: no parameters returns nothing. type EnemyFinder: interface consisting of FindEnemies: no parameters returns List of Enemy. type Radar: class implementing FoodWarmer, EnemyFinder and consisting of private secretMilitaryChips: List of Chip, private giantMicrowavesGenerator: FoodWarmerController, private strangeControlPanel: AlarmClock, public GetAccessToControlPanel: subprogram with no parameters returns FoodWarmerControlPanel, public OpenDoor: subprogram with no parameters returns nothing, public Put: subprogram with (Food) parameters return nothing, public CloseDoor: subprogram with no parameters returns nothing, public FindEnemies: subprogram with no parameters returns List of Enemy. type AlarmClock: class implementing FoodWarmerControlPanel and consisting of private mechanics: List of MechanicPart, public PressOn: subprogram with no parameters returns nothing, public PressOff: subprogram with no parameters returns nothing, public PressIncreaseTime: subprogram with no parameters returns nothing, public PressDecreaseTime: subprogram with no parameters returns nothing, public PressStart: subprogram with no parameters returns nothing, public PressStop: subprogram with no parameters returns nothing. type Microwave: class implementing FoodWarmer and consisting of private fancyInnerChips: List of Chip, private foodWarmingThing: FoodWarmerController, private buttonsPanel: ButtonsPanel, public GetAccessToControlPanel: subprogram with no parameters returns FoodWarmerControlPanel, public OpenDoor: subprogram with no parameters returns nothing, public Put: subprogram with (Food) parameters return nothing, public CloseDoor: subprogram with no parameters returns nothing. type ButtonsPanel: class implementing FoodWarmerControlPanel and consisting of private buttons: List of ButtonState, public PressOn: subprogram with no parameters returns nothing, public PressOff: subprogram with no parameters returns nothing, public PressIncreaseTime: subprogram with no parameters returns nothing, public PressDecreaseTime: subprogram with no parameters returns nothing, public PressStart: subprogram with no parameters returns nothing, public PressStop: subprogram with no parameters returns nothing. 

Ainsi, si une classe est déclarée implémentant une interface, elle doit implémenter toutes les méthodes de cette interface. Sinon, le compilateur nous dira "fi". Et nous avons deux interfaces: FoodWarmer et FoodWarmerControlPanel. Examinez-les attentivement, puis analysons les implémentations.


Comme héritage du passé soviétique difficile, nous avons reçu le radar de classe à double usage, qui peut être utilisé pour chauffer la nourriture et trouver l'ennemi. Et au lieu du panneau de contrôle, une alarme est utilisée, car le plan a été dépassé, et ils doivent être placés quelque part. Mais, heureusement, le MNS sans nom de l'Institut de recherche en chimie, engrais et poisons, sur lequel ils ont poussé, a mis en œuvre les interfaces FoodWarmer pour le radar et le FoodWarmerControlPanel pour le réveil.


Après une génération, il est apparu à quelqu'un qu'il vaut mieux chauffer les aliments avec un micro-ondes, et il est préférable de contrôler le micro-ondes avec des boutons. Et maintenant les classes Microwave et ButtonsPanel créées. Et ils implémentent les mêmes interfaces. FoodWarmer et FoodWarmerControl. Qu'est-ce que cela nous donne?


Si partout dans notre code nous avons utilisé une variable telle que FoodWarmer pour chauffer les aliments, nous pouvons simplement remplacer l'implémentation par une plus moderne et personne ne remarquera rien. Autrement dit, le code utilisant l'interface ne se soucie pas des détails d'implémentation. Ou au fait qu'il a complètement changé. Nous pouvons même créer la classe FoodWarmerFactory qui produit différentes implémentations FoodWarmer en fonction de la configuration de votre application.


Regardez également les champs fermés de la classe Microwave and Radar. Là, nous avons un réveil et un panneau avec des boutons. Mais à l'extérieur, nous donnons une variable de type FoodWarmerControlPanel.


Quelque part sur Picabu, il y avait une histoire sur la façon dont un certain candidat expliquait le principe du polymorphisme comme suit:

Ici, j'ai un stylo. Je peux lui écrire mon nom, mais je peux le coller dans tes yeux. C'est le principe du polymorphisme.
L'image est drôle, la situation est terrible et l'explication du principe du polymorphisme est inutile.

Le principe du polymorphisme n'est pas qu'une classe de stylos avec une certaine frayeur réalise à la fois les interfaces d'une papeterie et d'un acier froid. Le principe du polymorphisme est que tout ce qui peut être piqué peut être coincé dans l'œil. Parce qu'il peut être piqué. Et le résultat sera différent, mais idéalement devrait donner lieu à une privation visuelle. Et la méthode du polymorphisme vous permet de refléter ce fait dans le modèle que vous construisez pour votre monde.


Utilisation en dehors de la POO


Il y a une langue drôle et drôle dans tous les sens de ces mots comme Erlang. Et il a une telle fonctionnalité que le comportement. Surveillez vos mains:


Le code y est divisé en modules. Vous pouvez utiliser le nom du module comme variable. Autrement dit, vous pouvez écrire un appel de fonction à partir d'un module comme celui-ci:


 %option 1 foobar:function(), %option 2 Module = foobar, Module:function(). 

Afin d'être sûr que le module a certaines fonctions, il existe une telle caractéristique du langage que le comportement. Dans un module qui utilise d'autres modules, vous définissez les exigences pour les modules variables à l'aide de la déclaration behavior_info. Et puis les modules que votre module papa, qui a spécifié behavior_info, utilisera, en utilisant la déclaration de comportement, disent au compilateur: "nous nous engageons à implémenter ce comportement afin que le module papa puisse nous utiliser."


Par exemple, le module gen_server vous permet de créer un serveur qui, de manière synchrone ou asynchrone dans un processus séparé (il n'y a pas de threads dans Erlang, seulement des milliers de petits processus parallèles), exécute les requêtes d'autres processus. Et dans gen_server, toute la logique liée aux demandes provenant d'autres processus est collectée. Mais le traitement direct de ces requêtes est effectué par ceux qui implémentent le comportement de gen_server. Et tandis que d'autres modules l'implémentent correctement (même s'il y a des talons vides), gen_server ne se soucie même pas de la façon dont ces requêtes sont traitées. De plus, le module de traitement peut être changé à la volée.


Une interface - de nombreuses implémentations. Comme nous l'a légué Straustrup. Comme écrit dans les livres intelligents sur la POO. Et maintenant une citation de wikipedia au studio:


Erlang — , .

« — » , , .


. .NET. . CLR . CLR Microsoft , , , ( ECMA-335).


.NET , Windows, Windows Phone, XBox ( XNA, , ), . Microsoft. , , . , Mono Project. .NET. .


, . Microsoft , .NET . . , , .NET Core. , .NET Core .NET Framework, , , . , .


, « — » - . , .



, . , , , . , .


, , . , , NEPL. . Name? , . EtiquetteInfo - .


 import class EtiquetteInfo from Diplomacy. type PoliteName: class consisting of private FirstName: String, private MiddleName: String, private LastName: String, for descendants GetPoliteFirstName: subprogram with (EtiquetteInfo) parameters returns String, for descendants GetPoliteMiddleName: subprogram with (EtiquetteInfo) parameters returns String, for descendants GetPoliteLastName: subprogram with (EtiquetteInfo) parameters returns String, public GetFullName: subprogram with (EtiquetteInfo) parameters returns String. subprogram GetPoliteFirstName.PoliteName with (EtiquetteInfo _EtiquetteInfo) parameters returning String implemented as return _EtiquetteInfo.PoliteFirstName(FirstName). subprogram GetPoliteMiddleName.PoliteName with (EtiquetteInfo _EtiquetteInfo) parameters returning String implemented as return _EtiquetteInfo.PoliteMiddleName(MiddleName). subprogram GetPoliteLastName with (EtiquetteInfo _EtiquetteInfo) parameters returning String implemented as return _EtiquetteInfo.PoliteLastName(LastName). subprogram GetFullName with (EtiquetteInfo _EtiquetteInfo) parameters returning String implemented as return GetPoliteFirstName(_EtiquetteInfo) + GetPoliteMiddleName(_EtiquetteInfo) + GetPoliteLastName(_EtiquetteInfo). 

, GetFullName - , ( , , ?). , , - . , , . , , , , . , . PoliteName . ExoticPoliteName — . , , .


- . ExoticPoliteName, PoliteName, . PoliteExoticName. , PoliteName.


 import class EtiquetteInfo from Diplomacy. type PoliteExoticName: class extending PoliteName and consisting of private MoreMiddleNames: List of String, for descendants overridden GetPoliteMiddleName: subprogram with (EtiquetteInfo) parameters returns String, public overriden GetFullName: subprogram with (EtiquetteInfo) parameters returns String. subprogram GetPoliteMiddleName.PoliteExoticName with (EtiquetteInfo _EtiquetteInfo) parameters returning String implemented as String AggregatedMiddleName = String.Join(" ", MoreMiddleNames), return base.GetPoliteMiddleName(_EtiquetteInfo + AggregatedMiddleName). subprogram GetFullName with (EtiquetteInfo _EtiquetteInfo) parameters returning String implemented as String Prefix = "", String FirstName = GetFirstName(_EtiquetteInfo), if _EtiquetteInfo.ComplimentIsAppropriate(FirstName) then Prefix = "Oh, joy of my heart, dear ", return Prefix + base.GetFullName(_EtiquetteInfo). 

: PoliteName . PoliteExoticName -.


, , . , GetPoliteFirstName GetPoliteLastName. . GetFullName, , .


, , PoliteName, PoliteExoticName, GetFullName. , PoliteName, , . , , base.GetFullName(etiquetteInfo). , , .


, " ". , . : , . . .


, . . , Boolean, , . , Object. . , , , , Object, .


, NEPL . PoliteName Object, PoliteExoticName PoliteName Object . , NEPL :


 subprogram Foo.Bar with no parameters returning nothing implemented as PoliteExoticName _PoliteExoticName = GetSomePoliteExoticName(), PoliteName _PoliteName = _PoliteExoticName, Object _Object = _PoliteExoticName. 

, , _Object.GetFullName, , . PoliteName PoliteExoticName - Object, - _Object, .


? , . . , ( Object) , - -.


, , , , . ? , . - , . . - , .


? , . . . , «» , . ?


. , NEPL for descendants.


 type PoliteName: class consisting of private FirstName: String, private MiddleName: String, private LastName: String, for descendants GetPoliteFirstName: subprogram with (EtiquetteInfo) parameters returns String, for descendants GetPoliteMiddleName: subprogram with (EtiquetteInfo) parameters returns String, for descendants GetPoliteLastName: subprogram with (EtiquetteInfo) parameters returns String, public GetFullName: subprogram with (EtiquetteInfo) parameters returns String. 

PoliteExoticName FirstName, «, , , , ». GetPoliteFirstName FirstName.



, , Square Shape, Shape Square . , . Shape , , . Square, Shape. Pourquoi? , Shape, .


. , ? -, . -, , , , . , , .


, . , . « »? , . . , .


. , . , , . . , . , , .


, , . . ( , ), , . , , . , , . .


, ) , ) , , , 999 1000 . , , .


()


, . - , . , , . - , .


SOLID


— , , . - . SOLID — , , … ? ? , , .


S — The Single Responsibility Principle


. .


.

. . .


, « » - . . « ?». , , ? .


, SRP :

.

, ? — , . ? . « ». , . ? .


, ? . , .txt-. , , , .txt-. - , . , , … .txt-. Pourquoi? . , , .txt-.


NB ( , ) «», « , ».



. , , , .txt-. . , . , . Mais! -, , , . -, , , .



, , . , ) , ) .html-. , , .txt/.html.


, , , . , , .txt-. Qu'est-ce qui pourrait mal tourner?


  • . , . , , . , , , , , , . .
  • , . 900 USD 900.00$? 20190826T130000 2019 ? , ?
  • .txt-? .csv? , .txt. ? ? - ? - ? , ?

? — . , -.


, , , . , .


DRY — Don't Repeat Yourself


:


, . .

, , , - , . , , .



SRP . , , . , « ». , , , — .


. HTML . , «» . , HTML 90- . , . - , . , HTML- . CSS .


? -, CSS , «» . , . -, CSS- html- , - text-color . — . .


O — The Open/Closed Principle


, , , - . , «» «». / , , . « » . :


.

. , . , . , — .


, . . ? . , .


  • , . , . , / .
  • , . , . / /, . NB /. , : .
  • , . , .
  • , - , , . (), () «» .
  • . , , ? , . , . , .

, . .


Mauvais


 type SpellChecker: class consisting of public DoSpellCheck: subprogram with (String) parameters returns String. type CorporativeStyleChecker: class consisting of public DoCorporativeStyleCheck: subprogram with (String) parameters returns String. type TextProcessor: class consisting of private Text: String, private SpellChecker: SpellChecker, private CorporativeStyleChecker: CorporativeStyleChecker, public Process: subprogram with no parameters returns String. subprogram TextProcessor.Process with no parameters returning String implemented as String ProcessedText = Text, ProcessedText = SpellChecker.DoSpellCheck(ProcessedText), ProcessedText = CorporativeStyleChecker.DoCorporativeStyleCheck(ProcessedText), return ProcessedText. 

,


 type TextChecker: interface consisting of Check: have (String) parameters returns String. type SpellChecker: class implementing TextChecker and consisting of public Check: subprogram with (String) parameters returns String. type CorporativeStyleChecker: class implementing TextChecker and consisting of public Check: subprogram with (String) parameters returns String. type TextProcessor: class consisting of private Text: String, private SpellChecker: SpellChecker, private CorporativeStyleChecker: CorporativeStyleChecker, public Process: subprogram with no parameters returns String. subprogram TextProcessor.Process with no parameters returning String implemented as String ProcessedText = Text, List of SpellChecker Checkers = (SpellChecker, CorporativeStyleChecker), for each SpellChecker SpellChecker in Checkers do ProcessedText = SpellChecker.Check(ProcessedText) and nothing else, return ProcessedText. 

O/CP . TextCheckersSupplier, .


 type TextChecker: interface consisting of Check: have (String) parameters returns String. type SpellChecker: class implementing TextChecker and consisting of public Check: subprogram with (String) parameters returns String. type CorporativeStyleChecker: class implementing TextChecker and consisting of public Check: subprogram with (String) parameters returns String. type TextCheckersSupplier: class consisting of public GetCheckers: subprogram with no parameters returns List of TextChecker. type TextProcessor: class consisting of private Text: String, private CheckersSupplier: TextCheckersSupplier, public Process: subprogram with no parameters returns String. subprogram TextProcessor.Process with no parameters returning String implemented as String ProcessedText = Text, List of SpellChecker Checkers = CheckersSupplier.GetCheckers(), for each SpellChecker SpellChecker in Checkers do ProcessedText = SpellChecker.Check(ProcessedText) and nothing else, return ProcessedText. 

? , , , . , TextProcessor. , TextCheckerSupplier , , . TextChecker' . , , , . , .



, , , , . .


L — The Liskov Substitute Principle


, :


, , , .

, , . ?



- NEPL:


 type PoliteExoticName: class extending PoliteName and consisting of... subprogram Foo.Bar with no parameters returning nothing implemented as PoliteExoticName _PoliteExoticName = GetSomePoliteExoticName(), PoliteName _PoliteName = _PoliteExoticName, Object _Object = _PoliteExoticName. 

, _PoliteName - . , , . , . PoliteName, . , , , PoliteName . , , , . .


, -, allex ( , ). , , :

-, .

, «Agile Principles, Patterns and Practices in C#». NEPL, .


  Object _Object = GetObjectSomewhere(), PoliteExoticName IHopeItsActuallyName = _Object as PoliteExoticName, 

, , . . , . - , . , . . ( , alias , ):


 from UnboundedCollections import UnboundedSet as ThirdPartyUnboundedSet. from BoundedCollections import BoundedSet as ThirdPartBoundedSet. type Set: interface consisting of Add: have (Object) parameters returns nothing, Delete: have (Object) parameters returns nothing, IsMember: have (Object) parameters returns Boolean. type UnboundedSet: class implementing Set and consisting of private ThirdPartySet: ThirdPartyUnboundedSet, public Add: subprogram with (Object) parameters returning nothing, public Delete: subprogram with (Object) parameters returning nothing, public IsMember: subprogram with (Object) parameters returning Boolean. type BoundedSet: class implementing Set and consisting of private ThirdPartySet: ThirdPartyBoundedSet, public Add: subprogram with (Object) parameters returning nothing, public Delete: subprogram with (Object) parameters returning nothing, public IsMember: subprogram with (Object) parameters returning Boolean. subprogram BoundedSet.Add with (Object O) parameters returning nothing implemented as ThirdPartSet.Add(O). 


. . . PersistentSet, - . , PersistentObject. - . Delete IsMember . Add...


 from PersistentCollections import PersistentSet as ThirdPartyPersistentSet, PersistentObject. type PersistentSet: class implementing Set and consisting of private ThirdPartySet: ThirdPartyPersistentSet, public Add: subprogram with (Object) parameters returning nothing, public Delete: subprogram with (Object) parameters returning nothing, public IsMember: subprogram with (Object) parameters returning Boolean. subprogram PersistentSet.Add with (Object O) parameters returning nothing implemented as PersistentObject Po = O as PersistentObject, ThirdPartySet.Add(Po). 


. PersistentSet Object, . , , Set , . ( ):


 type MemberContainer: interface consisting of Delete: have (Object) parameters returns nothing, IsMember: have (Object) parameters returns Boolean. type Set: interface extending MemberContainer and consisting of Add: have (Object) parameters returns nothing. type PersistentSet: interface extending MemberContainer and consisting of Add: have (PersistingObject) parameters returns nothing. 


C#.


, NEPL

NEPL. List of String. , .


 type List: class generalized with (T) parameters consisting of 

Set PersistentSet.


 from UnboundedCollections import UnboundedSet as ThirdPartyUnboundedSet. from BoundedCollections import BoundedSet as ThirdPartBoundedSet. from PersistentCollections import PersistentSet as ThirdPartyPersistentSet, PersistentObject. type Set: interface generalized with (T) parameters consisting of Add: have (T) parameters returns nothing, Delete: have (T) parameters returns nothing, IsMember: have (T) parameters returns Boolean. type UnboundedSet: class implementing Set of Object and consisting of private ThirdPartySet: ThirdPartyUnboundedSet, public Add: subprogram with (Object) parameters returning nothing, public Delete: subprogram with (Object) parameters returning nothing, public IsMember: subprogram with (Object) parameters returning Boolean. type BoundedSet: class implementing Set of Object and consisting of private ThirdPartySet: ThirdPartyBoundedSet, public Add: subprogram with (Object) parameters returning nothing, public Delete: subprogram with (Object) parameters returning nothing, public IsMember: subprogram with (Object) parameters returning Boolean. type PersistentSet: class implementing Set of PersistentObject and consisting of private ThirdPartySet: ThirdPartyPersistentSet, public Add: subprogram with (PersistentObject) parameters returning nothing, public Delete: subprogram with (PersistentObject) parameters returning nothing, public IsMember: subprogram with (PersistentObject) parameters returning Boolean. 


()


. , , «» . , « — » . , . .


I — The Interface Segregation Principle


, . . .



, ? , - . , , SQL? , . , API . API , «interface», , . . Qu'est-ce qui pourrait mal tourner?


, , - , DBA . , 100500 , . . , .


, , , «» . , « interface, ». DBA . GDPR, HIPAA , .


- . , . .


? , . :


, ( ).

? interface «interface_billing», «interface_customer_data» . .


, , . pet-project. IActor. , . , IActor : ICollidable, IDisplayble, IUpdatable. ?



( , Camera), . , - . , . , , IDisplayble SpecEffect.


CollisionsController , - ICollidable. , , , SOLID . TileWall -, . CollisionsController . , , IActor , .



: , , .


D — The Dependency Inversion


-. , . , , . , , . , , , , - . , , - . , , - .


«Ici! .» — - ImportantClass. , , . , ImportantClass VeryImportantClass, , , EvenMoreImportantClass, , . , , , . , . , .



ImportantClass VeryImportantClass EvenMoreImportantClass. ImportantClass . , , . , IVeryImportantClass IEvenMoreImportantClass, ImportantClass.


ImportantClass VeryImportantClass . ImportantClass « », IVeryImportantClass .



. , , .


. .
. .

, «» «» - . , , . . , , . , . , -.


. , .


- , . ( MegaSender), . , , SOAP API.


-. SenderAccess. , MegaSender SenderAccess . SenderAccess MegaSender, , MegaSender, MegaSender , Apple i.


MegaSender. LightSender. , SenderAccess c LightSender. , , . , .


SenderAccess, , MegaSender . , SenderAccess MegaSender. MegaSender « ». , MegaSender, , , . . , LightSender , , LightSender, MegaSender.


, SenderAccess , SenderAccess LightSender . .


, IActor ICollidable, IUpdatable, IDisplayble. , IActor . Actor Player, Enemy, Door, Wall . , .


Blueprint. . , , , , , et cetera. , , , .


, , C#, . , - List<String>. , List<T> List<String>. Actor Actor<TBlueprint>.


, , - . Actor<EnemyBlueprint> Actor<DoorBlueprint> , . , .


- . , , . , . , . , IActor, , ActorsFactory .


. : .



, , . - , . () :


. . . TCP/HTTP/SMPP/SOAP, . ? , TCP/HTTP/SMPP/SOAP- TCP/HTTP/SMPP/SOAP- , . , - . ? Pensez-y. , « » « 1000 ».



. ? - SOLID'? , . , - , .


- , .
- , .

. , -, , . . - , , , , -. , .


. - , :


KISS — . . . , , . , , .


, , Actor . , — , - . , .


YAGNI — . - , , « », , - . , . « » - . , , . , , .


, OC/P . 50 . , , -. 50 , . , - .


? , , « », . « » , .


, SOLID . , .



, . , -, , . , .


  • «Code Complete» . , « » - .
  • «Clean Code» . «Clean Architecture».
  • «Agile Principles, Patterns and Practices in C#» . SOLID . , language-agnostic.

PS


, . , , . : ! , , language-agnostic .


, language-agnostic. NEPL, : , , , . , , .


, . , , . :


, , , . , , . , . : , .

Source: https://habr.com/ru/post/fr446816/


All Articles