Éléments de la POO. Principes de programmation orientée objet

Tous les langages orientés objet en utilisent trois principes de base programmation orientée objet:

  • Encapsulation. Comment langue donnée cache les fonctionnalités internes de l’implémentation de l’objet ?
  • Héritage. Comment le langage assure-t-il la réutilisabilité ? code de programme?
  • Polymorphisme. Comment un langage donné permet-il d’interpréter des objets liés de manière unifiée ?

Le premier principe de la POO est l’encapsulation. Encapsulation est un mécanisme de programmation qui relie le code et les données qu'il manipule, les protégeant ainsi accès externe et une application incorrecte et le masquage des détails de mise en œuvre au moyen du langage. Par exemple, disons que nous utilisons une classe qui représente des objets comme Pen. Une telle classe résume la capacité intrinsèque des objets à dessiner des points, des lignes et des formes d’épaisseurs et de couleurs variables. Le principe de l'encapsulation simplifie la tâche de programmation dans le sens où il n'y a plus besoin de se soucier des nombreuses lignes de code qui effectuent le travail de la classe pen en coulisses. Il suffit de créer une instance de la classe pen et d'interagir avec elle en appelant ses fonctions.

Un des aspects importants L'encapsulation est une protection des données. Idéalement, les données caractérisant l’état d’un objet devraient être définies comme fermées et inaccessibles à l’environnement extérieur. Dans ce cas, l'environnement extérieur de l'objet sera obligé de demander le droit de modifier ou de lire les valeurs correspondantes. Ainsi, le concept d'encapsulation reflète règle générale, selon lequel les champs de données d'un objet ne doivent pas être directement accessibles depuis interface ouverte. Si l'utilisateur a besoin de changer l'état d'un objet, il doit alors le faire non pas directement, mais indirectement, en utilisant les fonctions de lecture ( obtenir()) et modifications ( ensemble()). En C#, l'accessibilité des données est implémentée au niveau de la syntaxe à l'aide de mots clés publique, privé, protégé, Et interne protégé.

Au sein d'un objet, le code, les données, ou les deux, le code et les données peuvent être fermés aux autres objets ou ouverts. Le code et les données propriétaires sont connus et accessibles depuis une autre partie de cet objet uniquement (c'est-à-dire uniquement vers l'objet lui-même). Par conséquent, le code et les données propriétaires ne sont pas accessibles à partir d’une partie du programme qui existe en dehors de l’objet.

Le prochain principe de la POO est héritage , c'est-à-dire la capacité du langage à fournir la construction de définitions de nouvelles classes basées sur les définitions des classes existantes. Essentiellement, l'héritage vous permet d'étendre le comportement de la classe de base (également appelée classe parent) en construisant une sous-classe (appelée dérivé ou classe d'enfant), qui hérite des caractéristiques et des fonctionnalités de la classe parent. Essentiellement, cette forme d'héritage est la réutilisation du code de programme d'une classe (de base) dans d'autres classes (dérivées de celle-ci). Généralement, dans ce cas, la classe enfant étend les capacités de la classe de base en ajoutant de nouvelles fonctionnalités qui ne sont pas présentes dans la classe de base. Cette forme d'héritage est appelée « est-un » (c'est-à-dire être identique, mais avec de plus grandes capacités).


Une autre forme de réutilisation du code est le modèle de localisation/délégation (également connu sous le nom de relation de localisation « possède-un »). Ce formulaire n'est pas utilisé pour créer des relations classe-sous-classe. Au lieu de cela, une classe peut définir une variable d’une autre classe en elle-même et exposer tout ou partie de ses fonctionnalités au monde extérieur. Dans ce cas, la classe ressemble davantage à un conteneur contenant des instances d’autres classes.

Le troisième principe de la POO est polymorphisme . Il caractérise la capacité d’un langage à interpréter de la même manière des objets liés. Cette fonctionnalité d'un langage orienté objet permet à une classe de base de définir un ensemble de membres pour toutes les classes dérivées. Formellement, ce membre commun est appelé interface polymorphe. Une interface polymorphe pour une classe est construite en définissant un nombre arbitraire virtuel Et abstrait les fonctions. Fonction virtuelle classe Peut changement dans une classe dérivée, et une fonction abstraite peut être seulement passer outre. Lorsque les classes dérivées remplacent les fonctions définies dans une classe de base, elles remplacent essentiellement la manière dont elles répondent à une requête correspondante. En plus de la possibilité de remplacer des fonctions, le langage C# offre la possibilité d'utiliser une autre forme de polymorphisme : la surcharge de fonctions. La surcharge doit être considérée comme une capacité supplémentaire à distinguer les fonctions portant le même nom qui diffèrent par le nombre ou le type d'arguments. Par conséquent, la surcharge peut être appliquée non seulement aux fonctions membres de la classe, mais également aux fonctions globales.

Je ne sais pas programmer dans des langages orientés objet. Je n'ai pas appris. Après 5 ans de programmation industrielle en Java, je ne sais toujours pas créer bon système dans un style orienté objet. Je ne comprends tout simplement pas.

J'ai essayé d'apprendre, honnêtement. J'ai étudié des modèles, lu le code de projets open source, essayé de construire des concepts cohérents dans ma tête, mais je n'ai toujours pas compris les principes de création de programmes orientés objet de haute qualité. Peut-être que quelqu’un d’autre les a compris, mais pas moi.

Et voici quelques éléments qui me rendent confus.

Je ne sais pas ce qu'est la POO

Sérieusement. Il m'est difficile de formuler les idées principales de la POO. DANS programmation fonctionnelle L’une des idées principales est l’absence d’État. En structurel - décomposition. En modulaire, la fonctionnalité est divisée en blocs complets. Dans chacun de ces paradigmes, les principes dominants s'appliquent à 95 % du code et le langage est conçu pour encourager leur utilisation. Je ne connais pas de telles règles pour la POO.
  • Abstraction
  • Encapsulation
  • Héritage
  • Polymorphisme
Cela ressemble à un ensemble de règles, n'est-ce pas ? Voilà donc les règles à respecter dans 95 % des cas ? Hmm, regardons de plus près.

Abstraction

L'abstraction est un outil de programmation puissant. C'est ce qui nous permet de construire grands systèmes et en garder le contrôle. Il est peu probable que nous serions un jour approchés du niveau actuel des programmes si nous n'avions pas été armés d'un tel outil. Cependant, quel est le rapport entre l'abstraction et la POO ?

Premièrement, l’abstraction n’est pas un attribut exclusif de la POO ou de la programmation en général. Le processus de création de niveaux d’abstraction s’étend à presque tous les domaines de la connaissance humaine. Ainsi, nous pouvons porter un jugement sur les matériaux sans entrer dans les détails de leur structure moléculaire. Ou parler d’objets sans évoquer les matériaux avec lesquels ils sont fabriqués. Ou parlez de mécanismes complexes, comme un ordinateur, une turbine d’avion ou le corps humain, sans vous souvenir des détails individuels de ces entités.

Deuxièmement, il y a toujours eu des abstractions dans la programmation, à commencer par les écrits d'Ada Lovelace, considérée comme la première programmeuse de l'histoire. Depuis lors, les gens ont continuellement créé des abstractions dans leurs programmes, souvent avec uniquement les outils les plus simples. Ainsi, Abelson et Sussman, dans leur livre bien connu, décrivent comment créer un système de résolution d'équations prenant en charge des nombres complexes et même des polynômes, en utilisant uniquement des procédures et des listes chaînées. Alors, quels moyens d'abstraction supplémentaires la POO fournit-elle ? Je n'ai aucune idée. Séparer le code en sous-programmes ? N'importe quel langage de haut niveau peut le faire. Combiner les routines en un seul endroit ? Il y a suffisamment de modules pour cela. Typification ? Elle existait bien avant l’OLP. L'exemple d'un système de résolution d'équations montre clairement que la construction de niveaux d'abstraction ne dépend pas tant des outils du langage que des capacités du programmeur.

Encapsulation

Le principal avantage de l’encapsulation est de masquer l’implémentation. Le code client ne voit que l'interface, et ne peut s'appuyer que sur elle. Cela libère les développeurs qui peuvent décider de modifier l'implémentation. Et c'est vraiment cool. Mais la question est encore une fois : qu’est-ce que la POO a à voir avec cela ? Tous Les paradigmes ci-dessus impliquent de masquer la mise en œuvre. Lors de la programmation en C, vous allouez l'interface en fichiers d'en-tête, Oberon vous permet de créer des champs et des méthodes locaux au module, et enfin, l'abstraction dans de nombreux langages est construite simplement via des sous-programmes qui encapsulent également l'implémentation. De plus, les langages orientés objet eux-mêmes souvent violer la règle d'encapsulation, donnant accès aux données via des méthodes spéciales - getters et setters en Java, propriétés en C#, etc. (Dans les commentaires, nous avons découvert que certains objets dans les langages de programmation ne sont pas des objets du point de vue de la POO : les objets de transfert de données sont uniquement responsables du transfert de données et ne sont donc pas des entités POO à part entière, et donc il n'y a pas (Il est nécessaire qu'ils préservent l'encapsulation. D'un autre côté, les méthodes d'accès sont mieux préservées pour maintenir la flexibilité architecturale. C'est ainsi que cela se complique.) De plus, certains langages orientés objet, comme Python, n'essaient pas du tout de cacher quoi que ce soit. , mais comptez uniquement sur l'intelligence des développeurs utilisant ce code.

Héritage

L'héritage est l'une des rares nouveautés qui sont vraiment apparues grâce à la POO. Non, les langages orientés objet n'ont pas créé une nouvelle idée - l'héritage peut être implémenté dans n'importe quel autre paradigme - mais la POO a pour la première fois amené ce concept au niveau du langage lui-même. Les avantages de l'héritage sont également évidents : lorsque vous presque satisfait d'une classe, vous pouvez créer un descendant et remplacer une partie de ses fonctionnalités. Dans les langages prenant en charge l'héritage multiple, comme C++ ou Scala (dans ce dernier, via des traits), un autre cas d'utilisation apparaît : les mixins, de petites classes qui vous permettent de « mélanger » des fonctionnalités dans une nouvelle classe sans copier le code.

Alors, c'est ce qui distingue la POO en tant que paradigme des autres ? Hmm... si oui, pourquoi l'utilisons-nous si rarement dans le vrai code ? Vous vous souvenez de ce que j'ai dit à propos de 95 % du code obéissant aux règles du paradigme dominant ? Je ne plaisantais pas. En programmation fonctionnelle, au moins 95 % du code utilise des données et des fonctions immuables sans effets secondaires. En modulaire, presque tout le code est logiquement regroupé en modules. Les partisans de la programmation structurée, suivant les préceptes de Dijkstra, tentent de diviser toutes les parties du programme en petites parties. L'héritage est beaucoup moins fréquemment utilisé. Peut-être dans 10 % du code, peut-être dans 50 %, dans certains cas (par exemple, lors de l'héritage de classes framework) - dans 70 %, mais pas plus. Parce que dans la plupart des situations, c'est facile pas besoin.

De plus, l'héritage dangereux Pour bon design. Si dangereux que la Bande des Quatre (apparemment des prédicateurs de l’OLP) recommande dans son livre de la remplacer par une délégation chaque fois que cela est possible. L'héritage tel qu'il existe dans les langues actuellement populaires conduit à une conception fragile. Ayant été héritée d’un ancêtre, une classe ne peut plus être héritée des autres. Changer d’ancêtre devient aussi dangereux. Il existe bien sûr des modificateurs privés/protégés, mais ils nécessitent également des capacités psychiques considérables pour deviner comment la classe pourrait changer et comment le code client pourrait l'utiliser. L'héritage est si dangereux et peu pratique que les grands frameworks (tels que Spring et EJB en Java) l'abandonnent au profit d'autres outils non orientés objet (par exemple, la métaprogrammation). Les conséquences sont si imprévisibles que certaines bibliothèques (telles que Guava) attribuent à leurs classes des modificateurs qui interdisent l'héritage, et dans le nouveau langage Go, il a été décidé d'abandonner complètement la hiérarchie d'héritage.

Polymorphisme

Le polymorphisme est peut-être la meilleure chose à propos de la programmation orientée objet. Grâce au polymorphisme, en sortie, un objet de type Person ressemble à « Shandorkin Adam Impolitovich », et un objet de type Point ressemble à « ». C'est cela qui permet d'écrire « Mat1 * Mat2 » et d'obtenir le produit de matrices, similaire au produit de nombres ordinaires. Sans cela, il ne serait pas possible de lire les données du flux d'entrée, sans se soucier de savoir si elles proviennent du réseau, d'un fichier ou d'une ligne en mémoire. Partout où il y a des interfaces, le polymorphisme est également impliqué.

J'aime beaucoup le polymorphisme. Par conséquent, je ne parlerai même pas de ses problèmes dans les langues traditionnelles. Je garderai également le silence sur l'étroitesse de l'approche de répartition uniquement par type et sur la manière dont cela pourrait être fait. Dans la plupart des cas, cela fonctionne comme il se doit, ce qui n’est pas mauvais. La question est : le polymorphisme est-il le principe même qui distingue la POO des autres paradigmes ? Si vous me l’aviez demandé (et puisque vous lisez ce texte, vous pouvez supposer que vous l’avez demandé), j’aurais répondu « non ». Et la raison est toujours le même pourcentage d'utilisation dans le code. Peut-être que les interfaces et les méthodes polymorphes sont un peu plus courantes que l'héritage. Mais comparez le nombre de lignes de code qu'ils occupent avec le nombre de lignes écrites dans le style procédural habituel - il y en a toujours plus. En regardant les langages qui encouragent ce style de programmation, je ne les qualifierais pas de polymorphes. Langues prenant en charge le polymorphisme - oui, c'est normal. Mais pas les langues polymorphes.

(Cependant, c'est mon opinion. Vous pouvez toujours être en désaccord.)

Ainsi, l'abstraction, l'encapsulation, l'héritage et le polymorphisme - tout cela est en POO, mais rien de tout cela n'en fait partie intégrante. Alors qu’est-ce que la POO ? Il existe une opinion selon laquelle l'essence de la programmation orientée objet réside dans les objets (cela semble assez logique) et les classes. C'est l'idée de combiner du code et des données, et l'idée que les objets d'un programme reflètent des entités du monde réel. Nous reviendrons sur cet avis plus tard, mais mettons d’abord les points sur quelques i.

Quelle POO est la plus cool ?

De la partie précédente, il est clair que les langages de programmation peuvent différer considérablement dans la manière dont ils implémentent la programmation orientée objet. Si vous prenez la totalité de toutes les implémentations de POO dans toutes les langues, vous ne trouverez probablement pas une seule fonctionnalité commune à toutes. Afin de limiter d'une manière ou d'une autre ce zoo et de clarifier le raisonnement, je me concentrerai sur un seul groupe : les langages purement orientés objet, à savoir Java et C#. Le terme « purement orienté objet » signifie dans ce cas que le langage ne prend pas en charge d'autres paradigmes ni ne les implémente via la même POO. Python ou Ruby, par exemple, ne seront pas purs, car tu peux tout à fait écrire programme à part entière sur eux sans une seule déclaration de classe.

Pour mieux comprendre l'essence de la POO en Java et C#, regardons des exemples d'implémentation de ce paradigme dans d'autres langages.

Petite conversation. Contrairement à ses homologues modernes, ce langage était typé dynamiquement et utilisait un style de transmission de messages pour implémenter la POO. Au lieu d'appeler des méthodes, les objets s'envoyaient des messages, et si le destinataire ne pouvait pas traiter ce qui arrivait, il transmettait simplement le message à quelqu'un d'autre.

Lisp commun. Au départ, CL suivait le même paradigme. Ensuite, les développeurs ont décidé que l'écriture de `(send obj "some-message)` était trop longue et ont converti la notation en un appel de méthode - `(some-method obj)` Aujourd'hui, Common Lisp dispose d'un système de programmation orienté objet mature ( CLOS) avec prise en charge de l'héritage multiple, des multiméthodes et des métaclasses. Particularité est que la POO en CL ne tourne pas autour d'objets, mais autour de fonctions génériques.

Clojure. Clojure dispose de deux systèmes de programmation orientés objet : l'un hérité de Java et le second, basé sur des méthodes multiples et plus similaire à CLOS.

R. Ce langage d'analyse de données statistiques dispose également de 2 systèmes de programmation orientés objet - S3 et S4. Les deux sont hérités du langage S (ce qui n’est pas surprenant, étant donné que R est une implémentation open source du S commercial). S4 correspond en grande partie aux implémentations de POO dans les langages traditionnels modernes. S3 est une option plus légère, facile à implémenter à l'aide du langage lui-même : on en crée un fonction générale, distribuant les requêtes en fonction de l'attribut « class » de l'objet résultant.

JavaScript. Idéologiquement similaire à Smalltalk, bien qu'il utilise une syntaxe différente. Au lieu de l'héritage, il utilise le prototypage : si la propriété souhaitée ou la méthode appelée ne se trouve pas dans l'objet lui-même, alors la requête est transmise à l'objet prototype (la propriété prototype de tous les objets JavaScript). Un fait intéressant est que le comportement de tous les objets de classe peut être modifié en remplaçant l'une des méthodes prototypes (par exemple, l'ajout de la méthode `.toBASE64` pour la classe string semble très sympa).

Python. En général, il suit le même concept que les langages traditionnels, mais prend également en charge le passage de la recherche d'attribut à un autre objet, comme en JavaScript ou Smalltalk.

Haskell. Chez Haskell, il n’y a aucun état, et donc aucun objet au sens habituel du terme. Cependant, il existe toujours une sorte de POO : les types de données peuvent appartenir à une ou plusieurs classes de types. Par exemple, presque tous les types dans Haskell sont dans la classe Eq (responsable des opérations de comparaison entre 2 objets), et tous les nombres sont en outre dans les classes Num (opérations sur les nombres) et Ord (opérations sur les nombres).<, <=, >=, >). Dans les langages menstruels, les types correspondent à des classes (de données) et les classes de types correspondent à des interfaces.

Avec ou sans état ?

Mais revenons aux systèmes de programmation orientés objet les plus courants. Ce que je n'ai jamais pu comprendre, c'est la relation entre les objets et leur état interne. Avant d'étudier la POO, tout était simple et transparent : il existe des structures qui stockent plusieurs données liées, il existe des procédures (fonctions) qui les traitent. promener (chien), retirer (compte, montant). Ensuite, les objets sont arrivés, et c'était également bien (même si la lecture des programmes est devenue beaucoup plus difficile - mon chien se promenait [qui ?], et le compte retirait de l'argent [d'où ?]). Ensuite, j'ai découvert le masquage de données. Je pouvais encore promener le chien, mais je ne pouvais plus regarder la composition de sa nourriture. La nourriture n'a rien fait (on pourrait probablement écrire food.eat(dog), mais je préfère quand même que mon chien mange de la nourriture plutôt que l'inverse). La nourriture n’est que des données, et moi (et mon chien) avions juste besoin d’y accéder. Tous Juste. Mais il n'était plus possible de s'inscrire dans le cadre du paradigme, comme dans les vieux jeans de la fin des années 90.

D'accord, nous avons des méthodes d'accès aux données. Laissons-nous aller à cette petite illusion et prétendons que nos données sont réellement cachées. Mais maintenant je sais que les objets sont avant tout des données, et peut-être ensuite des méthodes qui les traitent. J'ai compris comment écrire des programmes, ce qu'il faut rechercher lors de la conception.

Avant d'avoir eu le temps de profiter de l'illumination, j'ai vu le mot apatride sur Internet (je jurerais qu'il était entouré de rayonnement et qu'un halo planait sur les lettres t et l). Une brève étude de la littérature a révélé le monde merveilleux du flux de contrôle transparent et du multithreading simple sans avoir besoin de suivre la cohérence des objets. Bien sûr, j’ai immédiatement eu envie de toucher à ce monde merveilleux. Cependant, cela signifiait un rejet complet de toute règle - il n'était plus clair si le chien devait se promener lui-même ou si un gestionnaire de promenade spécial était nécessaire pour cela ; avez-vous besoin d'un compte, ou la banque s'occupera-t-elle de tout le travail, et si oui, doit-elle amortir l'argent de manière statique ou dynamique, etc. Le nombre de cas d’utilisation a augmenté de façon exponentielle et tous les cas d’utilisation futurs pourraient nécessiter une refactorisation majeure.

Je ne sais toujours pas quand un objet doit être rendu apatride, quand il doit être avec état et quand il doit simplement être un conteneur de données. Parfois, c’est évident, mais la plupart du temps, ce n’est pas le cas.

Saisie : statique ou dynamique ?

Une autre chose que je ne peux pas décider à propos des langages comme C# et Java est de savoir s'ils sont typés statiquement ou dynamiquement. La plupart des gens s’écrieront probablement : « Quelle absurdité ! Typé statiquement bien sûr ! Les types sont vérifiés au moment de la compilation ! est-ce vraiment si simple? Est-il vrai qu'en spécifiant le type X dans les paramètres d'une méthode, un programmeur peut être sûr que les objets de type X lui seront toujours transmis ? C'est vrai, ce n'est pas possible, parce que... il sera possible de passer un paramètre de type X à la méthode X ou son héritier. Il semblerait, et alors ? Les descendants de la classe X auront toujours les mêmes méthodes que X. Les méthodes sont des méthodes, mais la logique de travail peut s'avérer complètement différente. Le cas le plus courant est celui où une classe enfant s'avère optimisée pour des besoins autres que X, et notre méthode peut compter exactement sur cette optimisation (si un tel scénario vous semble irréaliste, essayez d'écrire un plugin pour une bibliothèque open source développée - soit vous passerez plusieurs semaines à analyser l'architecture et les algorithmes de la bibliothèque, soit vous appellerez simplement des méthodes avec une signature adaptée au hasard). En conséquence, le programme fonctionne, mais la vitesse de fonctionnement diminue d'un ordre de grandeur. Bien que du point de vue du compilateur, tout soit correct. Il est significatif que Scala, appelé successeur de Java, autorise dans de nombreux endroits par défaut uniquement le passage d'arguments. type spécifié, bien que ce comportement puisse être modifié.

Un autre problème est la valeur null, qui peut être transmise à la place de presque n'importe quel objet en Java et à la place de n'importe quel objet Nullable en C#. null appartient à tous les types à la fois, et en même temps n'appartient à aucun. null n'a ni champs ni méthodes, donc tout appel (sauf la vérification de null) entraîne une erreur. Il semble que tout le monde soit habitué à cela, mais à titre de comparaison, Haskell (et le même Scala) est obligé d'utiliser des types spéciaux (Peut-être en Haskell, Option en Scala) pour envelopper des fonctions qui dans d'autres langages pourraient renvoyer null. En conséquence, on dit souvent à propos de Haskell "il est difficile de compiler un programme dessus, mais si vous réussissez, il est fort probable qu'il fonctionne correctement".

D'un autre côté, les langages traditionnels ne sont évidemment pas typés dynamiquement et n'ont donc pas de propriétés telles qu'une interface simple et des procédures flexibles. En conséquence, écrire en style Python ou Lisp devient également impossible.

Quelle différence cela fait-il de savoir comment s'appelle ce typage si toutes les règles sont connues de toute façon ? La différence réside dans le côté sous lequel vous abordez la conception de l’architecture. Il existe un débat de longue date sur la manière de construire un système : créer de nombreux types et peu de fonctions, ou peu de types et de nombreuses fonctions ? La première approche est activement utilisée en Haskell, la seconde en Lisp. Les langages modernes orientés objet utilisent quelque chose entre les deux. Je ne veux pas dire que c'est mauvais - cela a probablement ses avantages (après tout, il ne faut pas oublier que Java et C# sont des plateformes multilingues), mais chaque fois que je démarre un nouveau projet, je me demande par où commencer conception - avec des types ou à partir de fonctionnalités.

Et plus loin...

Je ne sais pas comment modéliser le problème. On pense que la POO vous permet d'afficher des objets du monde réel dans un programme. Cependant, en réalité j'ai un chien (avec deux oreilles, quatre pattes et un collier) et un compte bancaire (avec un gérant, des commis et une pause déjeuner), et dans le programme - WalkManager, AccountFactory... eh bien, vous obtenez l'idée. Et le fait n'est pas que le programme ait des classes auxiliaires qui ne reflètent pas les objets du monde réel. Le fait est que contrôler les changements de flux. Walking Manager me prive de la joie de promener mon chien et je reçois de l'argent sur un compte bancaire sans âme (hé, où est cette jolie fille sur laquelle j'ai changé de l'argent la semaine dernière ?).

Je suis peut-être snob, mais j'étais beaucoup plus heureux lorsque les données de l'ordinateur n'étaient que des données, même si elles décrivaient mon chien ou un compte bancaire. Je pouvais faire ce qui me convenait avec les données, sans égard au monde réel.

Je ne sais pas non plus comment décomposer correctement les fonctionnalités. En Python ou C++, si j'avais besoin d'une petite fonction pour convertir une chaîne en nombre, je l'écrivais simplement à la fin du fichier. En Java ou C#, je suis obligé de le mettre dans une classe StringUtils distincte. Dans les langages pré-OO, je pouvais déclarer un wrapper ad hoc pour renvoyer deux valeurs d'une fonction (le montant retiré et le solde du compte). Dans les langages POO, je devrai créer une classe de résultat de transaction à part entière. Et pour une nouvelle personne sur le projet (ou même pour moi-même une semaine plus tard), ce cours semblera tout aussi important et fondamental dans l'architecture du système. 150 fichiers, et tous tout aussi importants et fondamentaux – oh oui, une architecture transparente, de merveilleux niveaux d'abstraction.

Je ne sais pas comment écrire des programmes efficaces. Programmes efficaces utilisez peu de mémoire - sinon le garbage collector ralentira constamment l'exécution. Mais s'engager opération la plus simple dans les langages orientés objet il faut créer une dizaine d'objets. Pour en faire un Requête HTTP Je dois créer un objet de type URL, puis un objet de type HttpConnection, puis un objet de type Request... eh bien, vous voyez l'idée. En programmation procédurale, j'appellerais simplement plusieurs procédures en leur passant une structure créée sur la pile. Très probablement, un seul objet serait créé en mémoire : pour stocker le résultat. En POO, je dois encombrer la mémoire tout le temps.

Peut-être que la POO est un paradigme vraiment beau et élégant. Peut-être que je ne suis tout simplement pas assez intelligent pour le comprendre. Il y a probablement quelqu'un qui peut vraiment créer beau programme dans un langage orienté objet. Eh bien, je ne peux que les envier.

Pourquoi la programmation orientée objet est-elle préférée dans la plupart des projets ? La POO offre un moyen efficace de gérer leur complexité. Au lieu de visualiser un programme comme une séquence d'instructions exécutables, il le représente comme un groupe d'objets dotés de certaines propriétés et exécute certaines actions sur eux. Il en résulte des applications plus propres, plus fiables et plus maintenables.

Les principes de base ont émergé parce que des limites ont été découvertes dans les approches préexistantes. Parmi eux figurent l'accès illimité aux données et un grand nombre de connexions qui imposent des restrictions sur les modifications. Leur prise de conscience et leurs raisons sont importantes pour comprendre ce qu'est la POO en programmation et quels sont ses avantages.

Langages procéduraux

C, Pascal, FORTRAN et langages similaires sont procéduraux. C'est-à-dire que chacun de leurs opérateurs ordonne à l'ordinateur de faire quelque chose : recevoir des données, additionner des nombres, diviser par six, afficher le résultat. Une application dans un langage procédural est une liste d’instructions. S’il est petit, aucun autre principe organisateur (souvent appelé paradigme) n’est requis. Le programmeur crée une liste d'instructions et l'ordinateur les exécute.

Division en fonctions

À mesure que les applications s’allongent, la liste devient lourde. Peu de personnes peuvent comprendre plus de quelques centaines d’instructions tant qu’elles ne sont pas regroupées. Pour cette raison, cette fonctionnalité est devenue un moyen de rendre les applications plus compréhensibles pour leurs créateurs. Dans certaines langues, le même concept peut être appelé sous-programme ou procédure.

L'application est divisée en fonctions, chacune ayant un objectif et une interface clairement définis.

L'idée de diviser les procédures peut être étendue en les regroupant dans un objet plus grand appelé module, mais le principe est similaire : regrouper des composants qui exécutent des listes d'instructions.

La division en fonctions et modules est l’une des pierres angulaires de la programmation structurée, qui fut le paradigme dominant pendant plusieurs décennies avant l’avènement de la POO.

Problèmes de programmation structurée

À mesure que les applications devenaient plus volumineuses, la programmation structurée commençait à rencontrer des difficultés. Les projets devenaient trop complexes. Les horaires ont été décalés. Plus de programmeurs étaient impliqués. La complexité s’est accrue. Les coûts sont montés en flèche, le calendrier a été avancé et l’effondrement s’est ensuivi.

L’analyse des raisons de ces échecs a montré les lacunes du paradigme procédural. Quelle que soit la qualité de mise en œuvre d’une approche de programmation structurée, les applications volumineuses deviennent trop complexes.

Quelles sont les causes de ces problèmes liés aux langages procéduraux ? Premièrement, les fonctions ont un accès illimité aux données globales. Deuxièmement, des procédures et des valeurs sans rapport ne modélisent pas bien le monde réel.

Lorsque l’on considère ces questions dans le contexte d’un programme d’inventaire, l’un des éléments de données mondiaux les plus importants est la population des unités comptables. Diverses fonctions peut y accéder pour saisir une nouvelle valeur, l'afficher, la modifier, etc.

Accès illimité

Dans un programme écrit par exemple en C, il existe deux types de données. Les locaux sont cachés à l’intérieur de la fonction et ne sont pas utilisés par d’autres procédures.

Lorsque deux ou plusieurs fonctions doivent accéder aux mêmes données, celles-ci doivent être globales. Telles sont par exemple les informations sur les éléments pris en compte. Les données globales sont accessibles par n'importe quelle procédure.

DANS gros programme il existe de nombreuses fonctions et de nombreux éléments globaux. Le problème du paradigme procédural est qu’il conduit à encore plus de connexions potentielles entre eux.

Un si grand nombre de connexions soulève plusieurs difficultés. Premièrement, cela rend difficile la compréhension de la structure du programme. Deuxièmement, cela rend difficile les changements. Une modification apportée à une donnée globale peut nécessiter des ajustements de toutes les fonctions qui y accèdent.

Par exemple, dans un programme de comptabilité, quelqu'un décide que le code de l'article comptabilisé ne doit pas être composé de 5 chiffres, mais de 12. Cela nécessitera de passer du court au long. Les fonctions liées au code doivent maintenant être modifiées pour fonctionner avec le nouveau format.

Lorsque des éléments changent dans une application volumineuse, il est difficile de savoir quelles procédures y ont accès. Mais même si cela est compris, leur modification peut entraîner un fonctionnement incorrect d'autres données globales. Tout est lié à tout le reste, donc un changement dans un endroit se répercutera dans un autre.

Simulation du monde réel

Le deuxième problème, le plus important, du paradigme procédural est que son agencement de données et de fonctions individuelles ne modélise pas bien les choses du monde réel. Ici, nous avons affaire à des objets tels que des personnes et des voitures. Ils ne ressemblent pas à des données ou à des fonctions. Les objets réels complexes ont des attributs et un comportement.

Les attributs

Des exemples d'attributs (parfois appelés caractéristiques) pour les personnes sont la couleur des yeux et le titre du poste, et pour les voitures, la puissance et le nombre de portes. Il s’avère que les attributs du monde réel sont équivalents aux données d’un programme. Ils ont des significations spécifiques, comme le bleu (couleur des yeux) ou quatre (nombre de portes).

Comportement

Le comportement est ce que produisent les objets du monde réel en réponse à une sorte d’influence. Si vous demandez une augmentation de salaire à votre patron, la réponse sera « oui » ou « non ». Si vous appuyez sur le frein, la voiture s'arrêtera. Parler et s’arrêter sont des exemples de comportement. Un comportement est comme une procédure : il est appelé à faire quelque chose et il le fait. Ainsi, les données et les fonctions à elles seules ne modélisent pas efficacement les objets du monde réel.

Solution

Un objet en POO est représenté comme un ensemble de données et de fonctions. Seules les procédures, appelées fonctions membres en C++, permettent de récupérer ses valeurs. Les données sont masquées et protégées contre toute modification. Les valeurs et les fonctions sont regroupées en un tout. L'encapsulation et le masquage sont les termes principaux dans la description des langages OO.

Si vous devez modifier des données, vous savez exactement quelles fonctions interagissent avec elles. Aucune autre procédure ne peut y accéder. Cela facilite l'écriture, le débogage et la maintenance du programme.

Une application se compose généralement de plusieurs objets qui interagissent les uns avec les autres en appelant des fonctions membres.

Aujourd'hui, la programmation la plus utilisée est le C++ (plus-plus). Java manque de certaines fonctionnalités telles que les pointeurs, les modèles et l'héritage multiple, ce qui le rend moins puissant et polyvalent que C++. C# n’a pas encore atteint la popularité de C++.

Il convient de noter que les fonctions dites membres en C++ sont appelées méthodes dans certains autres langages orientés objet, tels que Smalltalk. Les éléments de données sont appelés attributs. Appeler une méthode sur un objet, c'est lui envoyer un message.

Analogie

Vous pouvez imaginer les objets comme des départements d’une entreprise. Dans la plupart des organisations, les employés ne travaillent pas aux RH un jour, ne payent pas le lendemain, puis étudient pendant une semaine. commerce de détail. Chaque département dispose de son propre personnel avec des responsabilités clairement assignées. Il existe également leurs propres données : indicateurs de salaires, ventes, dossiers des employés, etc. Les personnes des services travaillent avec leurs propres informations. Séparer une entreprise facilite ainsi le suivi de ses activités et le maintien de l’intégrité des données. Le service comptable est chargé de Si l'on a besoin de connaître le montant total des salaires versés dans la branche sud en juillet, inutile de fouiller dans les archives. Il suffit d'envoyer une note à la personne responsable, d'attendre que cette personne accède aux données et envoie une réponse avec les informations requises. Cela garantit le respect de la réglementation et l’absence d’interférence extérieure. De la même manière, un objet en POO assure l'organisation d'une application.

Il ne faut pas oublier que l'orientation objet ne concerne pas les détails du fonctionnement du programme. La plupart des instructions C++ correspondent à des opérateurs dans des langages procéduraux tels que C. En effet, les fonctions membres en C++ sont très similaires aux fonctions en C. Seul un contexte plus large déterminera si une instruction est procédurale ou orientée objet.

Objet en POO : définition

Lorsqu'on considère un problème de programmation dans un langage orienté objet, au lieu de se poser des questions sur sa division en fonctions distinctes, le problème de sa division en objets se pose. La pensée POO facilite grandement le développement d’applications. Cela se produit en raison de la similitude entre les logiciels et les objets réels.

Quelles choses deviennent des objets en POO ? Vous trouverez ci-dessous les catégories typiques.

Un objet physique en POO est :

  • transport dans des modèles de flux ;
  • éléments électriques dans les programmes de conception de circuits ;
  • pays dans le modèle économique ;
  • avions dans le système de contrôle du trafic aérien.

Éléments de l'environnement informatique de l'utilisateur :

  • menu;
  • fenêtre;
  • graphiques (ligne, rectangle, cercle) ;
  • clavier, souris, imprimante, lecteurs de disque.
  • ouvriers;
  • étudiants;
  • clients ;
  • les vendeurs.
  • Registre;
  • entreprise privée;
  • dictionnaire;
  • tableau des latitudes et longitudes des zones peuplées.

La connexion entre les objets du monde réel et la POO est le résultat de la combinaison de fonctions et de données : elles ont révolutionné la programmation. Il n’existe pas de correspondance aussi étroite dans les langages procéduraux.

Classe

Les objets en POO sont membres de classes. Qu'est-ce que ça veut dire? Les langages de programmation ont des types de données intégrés. Le type int, c'est-à-dire un entier, est prédéfini en C++. Vous pouvez déclarer autant de variables int que vous le souhaitez.

Un ensemble d'objets de la même classe est défini de la même manière. Il définit les fonctions et les données incluses dans ses objets sans les créer, tout comme int ne crée pas de variables.

Une classe en POO est une description d'un certain nombre d'objets similaires. Prince, Sting et Madonna sont chanteurs. Il n'y a personne avec ce nom, mais les gens peuvent être appelés ainsi s'ils possèdent les caractéristiques appropriées. Un objet POO est une instance d'une classe.

Héritage

Dans la vie, les classes sont divisées en sous-classes. Par exemple, les animaux sont divisés en amphibiens, mammifères, oiseaux, insectes, etc.

Le principe de ce type de division est que chaque sous-classe possède des caractéristiques communes avec la classe dont elle descend. Toutes les voitures ont des roues et un moteur. Ce sont les caractéristiques déterminantes Véhicule. En plus de caractéristiques générales Chaque sous-classe a ses propres caractéristiques. Il y a beaucoup de bus des places, et les camions ont de l'espace pour transporter de lourdes charges.

De même, une classe de base peut devenir le parent de plusieurs sous-classes dérivées, qui peuvent être définies pour partager ses caractéristiques tout en ajoutant les leurs. L'héritage est comme une fonction qui simplifie un programme procédural. Si plusieurs morceaux de code font presque la même chose, vous pouvez extraire les éléments communs et les regrouper dans une seule procédure. Trois sections de l'application peuvent appeler une fonction à exécuter actions générales, mais ils peuvent également effectuer leurs propres opérations. De même, une classe de base contient des données communes à un groupe de classes dérivées. Comme les fonctions, l'héritage raccourcit un programme orienté objet et clarifie les relations entre ses éléments.

Réutilisation

Une fois la classe créée et déboguée, elle peut être transmise à d'autres programmeurs pour réutilisation dans vos propres applications. C'est comme une bibliothèque de fonctions pouvant être incluses dans différentes applications.

En POO, l'héritage est une extension de l'idée de réutilisation. A partir d'une classe existante, sans la modifier, vous pouvez en créer une nouvelle avec l'ajout d'autres fonctions. La facilité de réutilisation des logiciels existants est un avantage important de la POO. On pense que cela permet d’augmenter le retour sur investissement initial.

Création de nouveaux types de données

Les objets sont utiles pour créer de nouveaux types de données. Supposons qu'un programme utilise des valeurs bidimensionnelles (par exemple, des coordonnées ou la latitude et la longitude) et que vous souhaitiez exprimer des opérations avec elles à l'aide d'opérations arithmétiques :

position1 = position + origine,

où et origine sont des paires de valeurs numériques indépendantes. Créer une classe qui inclut ces deux valeurs et déclarer les variables comme objets crée nouveau genre données.

Polymorphisme, surcharge

Les opérateurs = (égal) et + (plus) utilisés dans l'arithmétique positionnelle ci-dessus ne fonctionnent pas de la même manière qu'avec les types intégrés tels que int. Les objets de position, etc. ne sont pas prédéfinis, mais donnés par programmation. Comment ces opérateurs savent-ils les gérer ? La réponse est que de nouveaux modèles de comportement peuvent leur être attribués. Ces opérations seront des fonctions membres de la classe Position.

L'utilisation d'opérateurs ou de procédures en fonction de ce sur quoi ils opèrent est appelée polymorphisme. Lorsqu'un opérateur existant tel que + ou = a la possibilité de travailler avec un nouveau type de données, on dit qu'il est surchargé. La surcharge en POO est un type de polymorphisme. C'est sa caractéristique importante.

Le livre sur la POO « Programmation orientée objet pour les nuls » permettra à chacun de se familiariser plus en détail avec ce sujet.

Principes de base et étapes de l'orientation objet

la programmation

Dans la théorie de la programmation, la POO est définie comme une technologie de création de logiciels complexes, basée sur la représentation d'un programme comme un ensemble d'objets, dont chacun est une instance d'un type spécifique (classe), et les classes forment une hiérarchie avec

héritage de propriétés.

L'interaction des objets logiciels dans un tel système s'effectue par la transmission de messages.

Note. Cette représentation du programme a été utilisée pour la première fois dans le langage de simulation de systèmes complexes Simula, apparu dans les années 60.

La manière de représenter un programme, naturelle pour les langages de modélisation, a été développée dans un autre langage de modélisation spécialisé - le langage Smalltalk (années 70), puis a été

Page 2 sur 51

Principes de base de la POO

utilisé dans les nouvelles versions langues universelles programmation telle que Pascal, C++,

Le principal avantage de la POO- réduction du nombre d'appels intermodules et réduction de la quantité d'informations transférées entre modules,

comparé à programmation modulaire. Ceci est réalisé grâce à une localisation plus complète des données et à une intégration avec les routines de traitement,

ce qui permet un développement pratiquement indépendant de pièces individuelles

(objets) du programme.

De plus, l'approche objet offre de nouveaux outils de développement technologique, tels que héritage, polymorphisme, composition, remplissage,

vous permettant de construire des objets complexes à partir d'objets plus simples. En conséquence, le taux de réutilisation du code augmente considérablement,

il devient possible de créer des bibliothèques d'objets pour diverses applications, et les développeurs sont fournis caractéristiques supplémentaires créer des systèmes d’une complexité accrue.

Le principal inconvénient de la POO est une légère diminution des performances due à une organisation plus complexe du système logiciel.

La POO est basée sur les principes suivants : abstraction,

restriction d'accès, modularité, hiérarchie, typage, parallélisme,

durabilité.

Voyons ce qu'est chaque principe.

A b p s t r a g e- le processus d'identification des abstractions dans Domaine L'abstraction est un ensemble de caractéristiques essentielles d'un objet qui le distinguent de tous les autres types d'objets et,

ainsi, ils définissent clairement les caractéristiques d'un objet donné du point de vue d'un examen et d'une analyse plus approfondis. Conformément à la définition, l'abstraction utilisée d'un objet réel dépend significativement du problème à résoudre : dans un cas on s'intéressera à la forme de l'objet, dans un autre au poids, à

troisièmement - les matériaux à partir desquels il est fabriqué, quatrièmement - la loi du mouvement

Page 3 sur 51

Principes de base de la POO

sujet, etc Le niveau d'abstraction moderne implique l'unification de toutes les propriétés d'abstraction (à la fois celles liées à l'état de l'objet analysé,

et déterminer son comportement) en une seule unité de programme un certain

type abstrait (classe).

Limitation d'accès- dissimulation éléments individuels mise en œuvre d’une abstraction qui n’affecte pas ses caractéristiques essentielles dans son ensemble.

La nécessité de restreindre l'accès nécessite de distinguer deux parties dans la description de l'abstraction :

interface - un ensemble d'éléments de mise en œuvre de l'abstraction accessibles de l'extérieur (principales caractéristiques d'état et de comportement) ;

mise en œuvre - un ensemble d'éléments de mise en œuvre d'une abstraction inaccessibles de l'extérieur (l'organisation interne de l'abstraction et les mécanismes de mise en œuvre de son comportement).

Restreindre l'accès en POO permet au développeur de :

réaliser la construction du système par étapes, sans se laisser distraire par les caractéristiques de mise en œuvre des abstractions utilisées ;

il est facile de modifier l'implémentation d'objets individuels, ce qui, dans un système correctement organisé, ne nécessitera pas de modifications d'autres objets.

La combinaison consistant à combiner toutes les propriétés d'un objet (les composants de son état et de son comportement) en une seule abstraction et à restreindre l'accès à la mise en œuvre de ces propriétés est appelée encapsulation.

MODULARITÉ- le principe de développement d'un système logiciel,

suggérant sa mise en œuvre sous forme de parties distinctes (modules). Lors de la décomposition d'un système en modules, il est souhaitable de combiner des parties logiquement liées, garantissant, si possible, une réduction du nombre relations extérieures entre les modules. Le principe est hérité de

Page 4 sur 51

Principes de base de la POO

programmation modulaire, la suivre simplifie la conception et

débogage du programme.

La hiérarchie est un système d'abstractions classé ou ordonné.

Le principe de hiérarchie implique l'utilisation de hiérarchies dans le développement de systèmes logiciels.

La POO utilise deux types de hiérarchie.

Hiérarchie "tout/partie"- indique que certaines abstractions sont activées

dans l'abstraction en question comme ses parties, par exemple, une lampe se compose d'un culot, d'un filament incandescent et d'une ampoule. Cette version de la hiérarchie est utilisée dans le processus de division du système à différentes étapes de conception (au niveau logique - lors de la décomposition du domaine en objets, en niveau physique- lors de la décomposition du système en modules et lors de la séparation des processus individuels dans un système multiprocessus).

Hiérarchie "général/privé"- montre qu'une abstraction est un cas particulier d'une autre abstraction, par exemple "table à manger -

un type spécifique de table » et « les tables sont un type spécifique de mobilier ». Utilisé pour

développement de la structure de classe, lorsque des classes complexes sont construites sur la base de classes plus simples en leur ajoutant de nouvelles caractéristiques et, éventuellement, en clarifiant celles existantes.

L'un des mécanismes les plus importants de la POO est l'héritage des propriétés dans la hiérarchie publique/privée. L'héritage est une relation entre des abstractions lorsque l'une d'elles utilise une partie structurelle ou fonctionnelle d'une autre ou de plusieurs autres abstractions (respectivement simples et multiples).

héritage).

TIPZATION - une restriction imposée sur les propriétés des objets et

empêcher l’interchangeabilité des abstractions divers types(ou réduisant considérablement la possibilité d'un tel remplacement). Dans les langages fortement typés, pour chaque objet du programme (variable, sous-programme, paramètre, etc.)

déclare un type qui définit un ensemble d'opérations sur

Page 5 sur 51

Principes de base de la POO

objet logiciel correspondant. Les langages de programmation basés sur Pascal abordés ci-dessous utilisent strict, et ceux basés sur C -

degré moyen de typification.

L’utilisation du principe de typage garantit :

détection précoce des erreurs associées à des opérations invalides sur des objets de programme (les erreurs sont détectées au stade de la compilation du programme lors de la vérification de l'admissibilité d'effectuer une opération donnée sur un objet de programme) ;

simplification de la documentation;

la capacité de générer du code plus efficace.

Un type peut être associé à un objet logiciel de manière statique (le type de l'objet est déterminé au moment de la compilation - liaison anticipée) et dynamiquement (le type d'objet est déterminé uniquement lors de l'exécution du programme - reliure tardive). L'implémentation de la liaison tardive dans un langage de programmation permet de créer des variables - des pointeurs vers des objets appartenant à différentes classes (objets polymorphes), ce qui élargit considérablement les capacités de la langue.

P a r l l l e l i s m- la propriété de plusieurs abstractions d'être dans un état actif en même temps, c'est-à-dire effectuer certaines opérations.

Il existe un certain nombre de tâches dont la solution nécessite l'exécution simultanée de certaines séquences d'actions. Pour de telles tâches

par exemple, des tâches liées au contrôle automatique de plusieurs processus.

Le véritable parallélisme n'est obtenu que lors de la mise en œuvre de tâches de ce type sur des systèmes multiprocesseurs, lorsqu'il est possible d'exécuter chaque processus sur un processeur distinct. Les systèmes à processeur unique simulent le parallélisme en divisant le temps processeur entre les tâches de gestion de différents processus. Selon le type utilisé système opérateur(mono ou multi-programme)

Page 6 sur 51

Principes de base de la POO

le partage du temps peut être effectué soit par le système en cours de développement (comme dans

MS DOS), ou le système d'exploitation utilisé (comme dans les systèmes Windows).

La stabilité- la propriété de l'abstraction d'exister dans le temps quel que soit le processus qui y a donné naissance objet logiciel, et/ou dans l'espace, en partant de l'espace d'adressage dans lequel il a été créé.

Il y a:

∙ des objets temporaires qui stockent les résultats intermédiaires de certaines actions, telles que des calculs ;

∙ les objets locaux qui existent à l'intérieur des sous-programmes dont la durée de vie est calculée depuis l'appel du sous-programme jusqu'à son achèvement ;

∙ objets globaux qui existent pendant le chargement du programme en mémoire ;

∙ objets enregistrés dont les données sont stockées dans des fichiers mémoire externe entre les séances du programme.

Tous les principes ci-dessus sont mis en œuvre à un degré ou à un autre dans différentes versions langages orientés objet.

Langages de programmation orientés objet. Le langage est considéré orienté objet, s’il met en œuvre les quatre premiers des sept principes discutés.

Une place particulière est occupée modèles d'objet Delphi et C++Builder. Ces modèles généralisent l'expérience de la POO pour MS DOS et incluent quelques nouvelles fonctionnalités,

assurer la création efficace de systèmes plus complexes. Sur la base de ces modèles, des environnements visuels pour développer des applications Windows ont été créés.

La complexité de la programmation sous Windows a été considérablement surmontée

réduit en créant des bibliothèques spéciales d’objets qui « cachaient » de nombreux éléments des techniques de programmation.

Page 7 sur 51

Principes de base de la POO

Étapes de développement de systèmes logiciels utilisant la POO.

Le processus de développement logiciel utilisant la POO comprend quatre étapes : analyse, conception, évolution, modification.

Regardons ces étapes.

Analyse . Le but de l’analyse est de maximiser Description complète Tâches. À ce stade, une analyse du domaine problématique est effectuée, une décomposition des objets du système en cours de développement est effectuée et les caractéristiques les plus importantes du comportement des objets sont déterminées (description des abstractions). Sur la base des résultats de l'analyse, un schéma fonctionnel du produit logiciel est développé, qui montre les principaux objets et messages transmis entre eux, et décrit également les abstractions.

Conception Distinguer :

conception logique, dans lequel les décisions prises sont pratiquement indépendantes des conditions d'exploitation (système d'exploitation et équipements utilisés) ;

conception physique, dans lequel ces facteurs doivent être pris en compte.

Conception logique est de développer une structure de classe :

des champs pour stocker des composants de l'état des objets et des algorithmes de méthodes qui mettent en œuvre des aspects du comportement des objets sont définis. Dans ce cas, les techniques de développement de classe évoquées ci-dessus sont utilisées (héritage,

composition, contenu, polymorphisme, etc.). Le résultat est une hiérarchie ou un diagramme de classes montrant les relations entre les classes et une description des classes.

Conception physique comprend la combinaison des descriptions de classes en modules, le choix de leur schéma de connexion (disposition statique ou dynamique), la détermination des moyens d'interagir avec l'équipement, avec

système d'exploitation et/ou autre logiciel(Par exemple,

bases de données, programmes de réseau), assurant la synchronisation des processus pour les systèmes de traitement parallèles, etc.

Page 8 sur 51

Principes de base de la POO

Evolution des systèmes. Il s'agit d'un processus de mise en œuvre étape par étape et

connecter les classes au projet. Le processus commence par la création d'un programme ou d'un projet de base pour un futur produit logiciel. Ensuite, les classes sont implémentées et connectées de manière à créer un aperçu, mais, si possible,

prototype fonctionnel futur système. Il est testé et débogué.

Par exemple, un tel prototype peut être un système qui inclut la mise en œuvre de l'interface principale d'un produit logiciel (les messages ne sont pas transmis à une partie du système qui n'est pas encore disponible). Nous recevons ainsi un prototype fonctionnel du produit, qui peut, par exemple, être présenté au client pour clarifier ses exigences. Ensuite, le groupe de classes suivant est connecté au système, par exemple associé à la mise en œuvre d'un certain élément de menu.

La version résultante est également testée et déboguée, et ainsi de suite, jusqu'à ce que toutes les capacités du système soient réalisées.

L'utilisation d'une implémentation progressive simplifie considérablement les tests et le débogage d'un produit logiciel.

Modification. C'est le processus d'ajout de nouveaux Fonctionnalité ou modifier les propriétés du système existant. Généralement,

les changements affectent l'implémentation de la classe, laissant son interface inchangée, ce qui se fait généralement sans trop de problèmes lors de l'utilisation de la POO, car le processus de changement affecte la zone locale.

Le changement d'interface n'est pas non plus très bon tâche difficile, mais sa solution peut impliquer la nécessité de coordonner les processus d'interaction entre les objets, ce qui nécessitera des changements dans d'autres classes du programme. Cependant, la réduction du nombre de paramètres dans l'interface par rapport à la programmation modulaire simplifie grandement ce processus.

La facilité de modification rend l'adaptation relativement facile systèmes logiciels aux conditions d'exploitation changeantes, ce qui augmente la durée de vie des systèmes dont le développement nécessite énormément de temps et de ressources matérielles.

Page 9 sur 51

Principes de base de la POO

Une particularité de la POO est qu'un objet ou un groupe d'objets peut être développé séparément et, par conséquent, leur conception peut se situer à différentes étapes. Par exemple, les classes d'interface ont déjà été implémentées, mais la structure des classes de domaine est encore en cours de perfectionnement.

En règle générale, la conception commence lorsqu'un fragment du domaine est suffisamment décrit dans le processus d'analyse.

Nous commencerons notre considération des techniques de base de l’approche objet par la décomposition d’objets.

Décomposition d'objet

Comme mentionné ci-dessus, lors de l'utilisation de la technologie POO, la solution est représentée sous la forme le résultat de l'interaction d'éléments fonctionnels individuels un système qui simule des processus,

se produisant dans le domaine de la tâche.

Dans un tel système, tout le monde élément fonctionnel, ayant reçu une certaine influence d'entrée (appelée message) dans le processus de résolution du problème,

effectue des actions prédéterminées (par exemple, il peut changer son propre état, effectuer des calculs, dessiner une fenêtre ou un graphique et, à son tour, influencer d'autres éléments). Le processus de résolution de problèmes est contrôlé par séquence de messages. En transmettant ces messages d'élément en élément, le système effectue les actions nécessaires.

Éléments fonctionnels du système dont les paramètres et le comportement sont déterminés par l'état du problème et ont un comportement indépendant

(c'est-à-dire « capables » d'effectuer certaines actions en fonction des messages reçus et de l'état de l'élément) sont appelés objets.

Le processus de représentation du domaine problématique sous la forme d'un ensemble d'objets échangeant des messages est appelé décomposition d'un objet.

Page 10 sur 51

Principes de base de la POO

Afin de comprendre quels objets et quels messages sont discutés lors de la décomposition d'objets dans chaque cas spécifique, il convient de rappeler que l'approche objet a été initialement proposée pour le développement de modèles de simulation du comportement de systèmes complexes. L'ensemble des objets de tels systèmes est généralement déterminé en analysant les processus simulés.

Exemple. Décomposition d'objets (modèle de simulation

les stations-service). Intéressons-nous à la dépendance de la longueur de la file d'attente dans une station-service au nombre de stations-service, aux paramètres de service de chaque station-service et à l'intensité des demandes de ravitaillement (nous considérons le même type de carburant).

Les problèmes de ce type sont généralement résolus à l’aide de modèles de simulation. Le modèle simule par programme un processus réel avec des paramètres donnés, en enregistrant simultanément ses caractéristiques. En répétant plusieurs fois le processus de simulation avec différentes valeurs de paramètres de service ou de réception de demandes, le chercheur obtient des valeurs spécifiques des caractéristiques sur lesquelles sont construits les graphiques des dépendances analysées.

Le processus d'exploitation d'une station-service avec trois stations-service peut être représenté sous la forme d'un diagramme.

Je ne sais pas programmer dans des langages orientés objet. Je n'ai pas appris. Après 5 ans de programmation industrielle en Java, je ne sais toujours pas comment créer un bon système dans un style orienté objet. Je ne comprends tout simplement pas.

J'ai essayé d'apprendre, honnêtement. J'ai étudié des modèles, lu le code de projets open source, essayé de construire des concepts cohérents dans ma tête, mais je n'ai toujours pas compris les principes de création de programmes orientés objet de haute qualité. Peut-être que quelqu’un d’autre les a compris, mais pas moi.

Et voici quelques éléments qui me rendent confus.

Je ne sais pas ce qu'est la POO

Sérieusement. Il m'est difficile de formuler les idées principales de la POO. En programmation fonctionnelle, l’une des idées principales est l’apatridie. En structurel - décomposition. En modulaire, la fonctionnalité est divisée en blocs complets. Dans chacun de ces paradigmes, les principes dominants s'appliquent à 95 % du code et le langage est conçu pour encourager leur utilisation. Je ne connais pas de telles règles pour la POO.
  • Abstraction
  • Encapsulation
  • Héritage
  • Polymorphisme
Cela ressemble à un ensemble de règles, n'est-ce pas ? Voilà donc les règles à respecter dans 95 % des cas ? Hmm, regardons de plus près.

Abstraction

L'abstraction est un outil de programmation puissant. C’est ce qui nous permet de construire de grands systèmes et d’en garder le contrôle. Il est peu probable que nous serions un jour approchés du niveau actuel des programmes si nous n'avions pas été armés d'un tel outil. Cependant, quel est le rapport entre l'abstraction et la POO ?

Premièrement, l’abstraction n’est pas un attribut exclusif de la POO ou de la programmation en général. Le processus de création de niveaux d’abstraction s’étend à presque tous les domaines de la connaissance humaine. Ainsi, nous pouvons porter un jugement sur les matériaux sans entrer dans les détails de leur structure moléculaire. Ou parler d’objets sans évoquer les matériaux avec lesquels ils sont fabriqués. Ou parlez de mécanismes complexes, comme un ordinateur, une turbine d’avion ou le corps humain, sans vous souvenir des détails individuels de ces entités.

Deuxièmement, il y a toujours eu des abstractions dans la programmation, à commencer par les écrits d'Ada Lovelace, considérée comme la première programmeuse de l'histoire. Depuis lors, les gens ont continuellement créé des abstractions dans leurs programmes, souvent avec uniquement les outils les plus simples. Ainsi, Abelson et Sussman, dans leur livre bien connu, décrivent comment créer un système de résolution d'équations prenant en charge des nombres complexes et même des polynômes, en utilisant uniquement des procédures et des listes chaînées. Alors, quels moyens d'abstraction supplémentaires la POO fournit-elle ? Je n'ai aucune idée. Séparer le code en sous-programmes ? N'importe quel langage de haut niveau peut le faire. Combiner les routines en un seul endroit ? Il y a suffisamment de modules pour cela. Typification ? Elle existait bien avant l’OLP. L'exemple d'un système de résolution d'équations montre clairement que la construction de niveaux d'abstraction ne dépend pas tant des outils du langage que des capacités du programmeur.

Encapsulation

Le principal avantage de l’encapsulation est de masquer l’implémentation. Le code client ne voit que l'interface, et ne peut s'appuyer que sur elle. Cela libère les développeurs qui peuvent décider de modifier l'implémentation. Et c'est vraiment cool. Mais la question est encore une fois : qu’est-ce que la POO a à voir avec cela ? Tous Les paradigmes ci-dessus impliquent de masquer la mise en œuvre. Lors de la programmation en C, vous allouez l'interface en fichiers d'en-tête, Oberon vous permet de créer des champs et des méthodes locaux au module, et enfin, l'abstraction dans de nombreux langages est construite simplement via des sous-programmes qui encapsulent également l'implémentation. De plus, les langages orientés objet eux-mêmes souvent violer la règle d'encapsulation, donnant accès aux données via des méthodes spéciales - getters et setters en Java, propriétés en C#, etc. (Dans les commentaires, nous avons découvert que certains objets dans les langages de programmation ne sont pas des objets du point de vue de la POO : les objets de transfert de données sont uniquement responsables du transfert de données et ne sont donc pas des entités POO à part entière, et donc il n'y a pas (Il est nécessaire qu'ils préservent l'encapsulation. D'un autre côté, les méthodes d'accès sont mieux préservées pour maintenir la flexibilité architecturale. C'est ainsi que cela se complique.) De plus, certains langages orientés objet, comme Python, n'essaient pas du tout de cacher quoi que ce soit. , mais comptez uniquement sur l'intelligence des développeurs utilisant ce code.

Héritage

L'héritage est l'une des rares nouveautés qui sont vraiment apparues grâce à la POO. Non, les langages orientés objet n'ont pas créé une nouvelle idée - l'héritage peut être implémenté dans n'importe quel autre paradigme - mais la POO a pour la première fois amené ce concept au niveau du langage lui-même. Les avantages de l'héritage sont également évidents : lorsque vous presque satisfait d'une classe, vous pouvez créer un descendant et remplacer une partie de ses fonctionnalités. Dans les langages prenant en charge l'héritage multiple, comme C++ ou Scala (dans ce dernier, via des traits), un autre cas d'utilisation apparaît : les mixins, de petites classes qui vous permettent de « mélanger » des fonctionnalités dans une nouvelle classe sans copier le code.

Alors, c'est ce qui distingue la POO en tant que paradigme des autres ? Hmm... si oui, pourquoi l'utilisons-nous si rarement dans le vrai code ? Vous vous souvenez de ce que j'ai dit à propos de 95 % du code obéissant aux règles du paradigme dominant ? Je ne plaisantais pas. En programmation fonctionnelle, au moins 95 % du code utilise des données et des fonctions immuables sans effets secondaires. En modulaire, presque tout le code est logiquement regroupé en modules. Les partisans de la programmation structurée, suivant les préceptes de Dijkstra, tentent de diviser toutes les parties du programme en petites parties. L'héritage est beaucoup moins fréquemment utilisé. Peut-être dans 10 % du code, peut-être dans 50 %, dans certains cas (par exemple, lors de l'héritage de classes framework) - dans 70 %, mais pas plus. Parce que dans la plupart des situations, c'est facile pas besoin.

De plus, l'héritage dangereux pour une bonne conception. Si dangereux que la Bande des Quatre (apparemment des prédicateurs de l’OLP) recommande dans son livre de la remplacer par une délégation chaque fois que cela est possible. L'héritage tel qu'il existe dans les langues actuellement populaires conduit à une conception fragile. Ayant été héritée d’un ancêtre, une classe ne peut plus être héritée des autres. Changer d’ancêtre devient aussi dangereux. Il existe bien sûr des modificateurs privés/protégés, mais ils nécessitent également des capacités psychiques considérables pour deviner comment la classe pourrait changer et comment le code client pourrait l'utiliser. L'héritage est si dangereux et peu pratique que les grands frameworks (tels que Spring et EJB en Java) l'abandonnent au profit d'autres outils non orientés objet (par exemple, la métaprogrammation). Les conséquences sont si imprévisibles que certaines bibliothèques (telles que Guava) attribuent à leurs classes des modificateurs qui interdisent l'héritage, et dans le nouveau langage Go, il a été décidé d'abandonner complètement la hiérarchie d'héritage.

Polymorphisme

Le polymorphisme est peut-être la meilleure chose à propos de la programmation orientée objet. Grâce au polymorphisme, en sortie, un objet de type Person ressemble à « Shandorkin Adam Impolitovich », et un objet de type Point ressemble à « ». C'est cela qui permet d'écrire « Mat1 * Mat2 » et d'obtenir le produit de matrices, similaire au produit de nombres ordinaires. Sans cela, il ne serait pas possible de lire les données du flux d'entrée, sans se soucier de savoir si elles proviennent du réseau, d'un fichier ou d'une ligne en mémoire. Partout où il y a des interfaces, le polymorphisme est également impliqué.

J'aime beaucoup le polymorphisme. Par conséquent, je ne parlerai même pas de ses problèmes dans les langues traditionnelles. Je garderai également le silence sur l'étroitesse de l'approche de répartition uniquement par type et sur la manière dont cela pourrait être fait. Dans la plupart des cas, cela fonctionne comme il se doit, ce qui n’est pas mauvais. La question est : le polymorphisme est-il le principe même qui distingue la POO des autres paradigmes ? Si vous me l’aviez demandé (et puisque vous lisez ce texte, vous pouvez supposer que vous l’avez demandé), j’aurais répondu « non ». Et la raison est toujours le même pourcentage d'utilisation dans le code. Peut-être que les interfaces et les méthodes polymorphes sont un peu plus courantes que l'héritage. Mais comparez le nombre de lignes de code qu'ils occupent avec le nombre de lignes écrites dans le style procédural habituel - il y en a toujours plus. En regardant les langages qui encouragent ce style de programmation, je ne les qualifierais pas de polymorphes. Langues prenant en charge le polymorphisme - oui, c'est normal. Mais pas les langues polymorphes.

(Cependant, c'est mon opinion. Vous pouvez toujours être en désaccord.)

Ainsi, l'abstraction, l'encapsulation, l'héritage et le polymorphisme - tout cela est en POO, mais rien de tout cela n'en fait partie intégrante. Alors qu’est-ce que la POO ? Il existe une opinion selon laquelle l'essence de la programmation orientée objet réside dans les objets (cela semble assez logique) et les classes. C'est l'idée de combiner du code et des données, et l'idée que les objets d'un programme reflètent des entités du monde réel. Nous reviendrons sur cet avis plus tard, mais mettons d’abord les points sur quelques i.

Quelle POO est la plus cool ?

De la partie précédente, il est clair que les langages de programmation peuvent différer considérablement dans la manière dont ils implémentent la programmation orientée objet. Si vous prenez la totalité de toutes les implémentations de POO dans toutes les langues, vous ne trouverez probablement pas une seule fonctionnalité commune à toutes. Afin de limiter d'une manière ou d'une autre ce zoo et de clarifier le raisonnement, je me concentrerai sur un seul groupe : les langages purement orientés objet, à savoir Java et C#. Le terme « purement orienté objet » signifie dans ce cas que le langage ne prend pas en charge d'autres paradigmes ni ne les implémente via la même POO. Python ou Ruby, par exemple, ne seront pas purs, car vous pouvez facilement écrire un programme à part entière sans une seule déclaration de classe.

Pour mieux comprendre l'essence de la POO en Java et C#, regardons des exemples d'implémentation de ce paradigme dans d'autres langages.

Petite conversation. Contrairement à ses homologues modernes, ce langage était typé dynamiquement et utilisait un style de transmission de messages pour implémenter la POO. Au lieu d'appeler des méthodes, les objets s'envoyaient des messages, et si le destinataire ne pouvait pas traiter ce qui arrivait, il transmettait simplement le message à quelqu'un d'autre.

Lisp commun. Au départ, CL suivait le même paradigme. Ensuite, les développeurs ont décidé que l'écriture de `(send obj "some-message)` était trop longue et ont converti la notation en un appel de méthode - `(some-method obj)` Aujourd'hui, Common Lisp dispose d'un système de programmation orienté objet mature ( CLOS) avec prise en charge de l'héritage multiple, des multiméthodes et des métaclasses. Une particularité est que la POO en CL ne tourne pas autour d'objets, mais autour de fonctions génériques.

Clojure. Clojure dispose de deux systèmes de programmation orientés objet : l'un hérité de Java et le second, basé sur des méthodes multiples et plus similaire à CLOS.

R. Ce langage d'analyse de données statistiques dispose également de 2 systèmes de programmation orientés objet - S3 et S4. Les deux sont hérités du langage S (ce qui n’est pas surprenant, étant donné que R est une implémentation open source du S commercial). S4 correspond en grande partie aux implémentations de POO dans les langages traditionnels modernes. S3 est une option plus légère, simplement implémentée en utilisant le langage lui-même : une fonction générale est créée qui envoie les requêtes en fonction de l'attribut « class » de l'objet reçu.

JavaScript. Idéologiquement similaire à Smalltalk, bien qu'il utilise une syntaxe différente. Au lieu de l'héritage, il utilise le prototypage : si la propriété souhaitée ou la méthode appelée ne se trouve pas dans l'objet lui-même, alors la requête est transmise à l'objet prototype (la propriété prototype de tous les objets JavaScript). Un fait intéressant est que le comportement de tous les objets de classe peut être modifié en remplaçant l'une des méthodes prototypes (par exemple, l'ajout de la méthode `.toBASE64` pour la classe string semble très sympa).

Python. En général, il suit le même concept que les langages traditionnels, mais prend également en charge le passage de la recherche d'attribut à un autre objet, comme en JavaScript ou Smalltalk.

Haskell. Chez Haskell, il n’y a aucun état, et donc aucun objet au sens habituel du terme. Cependant, il existe toujours une sorte de POO : les types de données peuvent appartenir à une ou plusieurs classes de types. Par exemple, presque tous les types dans Haskell sont dans la classe Eq (responsable des opérations de comparaison entre 2 objets), et tous les nombres sont en outre dans les classes Num (opérations sur les nombres) et Ord (opérations sur les nombres).<, <=, >=, >). Dans les langages menstruels, les types correspondent à des classes (de données) et les classes de types correspondent à des interfaces.

Avec ou sans état ?

Mais revenons aux systèmes de programmation orientés objet les plus courants. Ce que je n'ai jamais pu comprendre, c'est la relation entre les objets et leur état interne. Avant d'étudier la POO, tout était simple et transparent : il existe des structures qui stockent plusieurs données liées, il existe des procédures (fonctions) qui les traitent. promener (chien), retirer (compte, montant). Ensuite, les objets sont arrivés, et c'était également bien (même si la lecture des programmes est devenue beaucoup plus difficile - mon chien se promenait [qui ?], et le compte retirait de l'argent [d'où ?]). Ensuite, j'ai découvert le masquage de données. Je pouvais encore promener le chien, mais je ne pouvais plus regarder la composition de sa nourriture. La nourriture n'a rien fait (on pourrait probablement écrire food.eat(dog), mais je préfère quand même que mon chien mange de la nourriture plutôt que l'inverse). La nourriture n’est que des données, et moi (et mon chien) avions juste besoin d’y accéder. Tous Juste. Mais il n'était plus possible de s'inscrire dans le cadre du paradigme, comme dans les vieux jeans de la fin des années 90.

D'accord, nous avons des méthodes d'accès aux données. Laissons-nous aller à cette petite illusion et prétendons que nos données sont réellement cachées. Mais maintenant je sais que les objets sont avant tout des données, et peut-être ensuite des méthodes qui les traitent. J'ai compris comment écrire des programmes, ce qu'il faut rechercher lors de la conception.

Avant d'avoir eu le temps de profiter de l'illumination, j'ai vu le mot apatride sur Internet (je jurerais qu'il était entouré de rayonnement et qu'un halo planait sur les lettres t et l). Une brève étude de la littérature a révélé le monde merveilleux du flux de contrôle transparent et du multithreading simple sans avoir besoin de suivre la cohérence des objets. Bien sûr, j’ai immédiatement eu envie de toucher à ce monde merveilleux. Cependant, cela signifiait un rejet complet de toute règle - il n'était plus clair si le chien devait se promener lui-même ou si un gestionnaire de promenade spécial était nécessaire pour cela ; avez-vous besoin d'un compte, ou la banque s'occupera-t-elle de tout le travail, et si oui, doit-elle amortir l'argent de manière statique ou dynamique, etc. Le nombre de cas d’utilisation a augmenté de façon exponentielle et tous les cas d’utilisation futurs pourraient nécessiter une refactorisation majeure.

Je ne sais toujours pas quand un objet doit être rendu apatride, quand il doit être avec état et quand il doit simplement être un conteneur de données. Parfois, c’est évident, mais la plupart du temps, ce n’est pas le cas.

Saisie : statique ou dynamique ?

Une autre chose que je ne peux pas décider à propos des langages comme C# et Java est de savoir s'ils sont typés statiquement ou dynamiquement. La plupart des gens s’écrieront probablement : « Quelle absurdité ! Typé statiquement bien sûr ! Les types sont vérifiés au moment de la compilation ! est-ce vraiment si simple? Est-il vrai qu'en spécifiant le type X dans les paramètres d'une méthode, un programmeur peut être sûr que les objets de type X lui seront toujours transmis ? C'est vrai, ce n'est pas possible, parce que... il sera possible de passer un paramètre de type X à la méthode X ou son héritier. Il semblerait, et alors ? Les descendants de la classe X auront toujours les mêmes méthodes que X. Les méthodes sont des méthodes, mais la logique de travail peut s'avérer complètement différente. Le cas le plus courant est celui où une classe enfant s'avère optimisée pour des besoins autres que X, et notre méthode peut compter exactement sur cette optimisation (si un tel scénario vous semble irréaliste, essayez d'écrire un plugin pour une bibliothèque open source développée - soit vous passerez plusieurs semaines à analyser l'architecture et les algorithmes de la bibliothèque, soit vous appellerez simplement des méthodes avec une signature adaptée au hasard). En conséquence, le programme fonctionne, mais la vitesse de fonctionnement diminue d'un ordre de grandeur. Bien que du point de vue du compilateur, tout soit correct. Il est significatif que Scala, appelé successeur de Java, autorise dans de nombreux endroits par défaut la transmission de seuls arguments du type spécifié, bien que ce comportement puisse être modifié.

Un autre problème est la valeur null, qui peut être transmise à la place de presque n'importe quel objet en Java et à la place de n'importe quel objet Nullable en C#. null appartient à tous les types à la fois, et en même temps n'appartient à aucun. null n'a ni champs ni méthodes, donc tout appel (sauf la vérification de null) entraîne une erreur. Il semble que tout le monde soit habitué à cela, mais à titre de comparaison, Haskell (et le même Scala) est obligé d'utiliser des types spéciaux (Peut-être en Haskell, Option en Scala) pour envelopper des fonctions qui dans d'autres langages pourraient renvoyer null. En conséquence, on dit souvent à propos de Haskell "il est difficile de compiler un programme dessus, mais si vous réussissez, il est fort probable qu'il fonctionne correctement".

D'un autre côté, les langages traditionnels ne sont évidemment pas typés dynamiquement et n'ont donc pas de propriétés telles qu'une interface simple et des procédures flexibles. En conséquence, écrire en style Python ou Lisp devient également impossible.

Quelle différence cela fait-il de savoir comment s'appelle ce typage si toutes les règles sont connues de toute façon ? La différence réside dans le côté sous lequel vous abordez la conception de l’architecture. Il existe un débat de longue date sur la manière de construire un système : créer de nombreux types et peu de fonctions, ou peu de types et de nombreuses fonctions ? La première approche est activement utilisée en Haskell, la seconde en Lisp. Les langages modernes orientés objet utilisent quelque chose entre les deux. Je ne veux pas dire que c'est mauvais - cela a probablement ses avantages (après tout, il ne faut pas oublier que Java et C# sont des plateformes multilingues), mais chaque fois que je démarre un nouveau projet, je me demande par où commencer conception - avec des types ou à partir de fonctionnalités.

Et plus loin...

Je ne sais pas comment modéliser le problème. On pense que la POO vous permet d'afficher des objets du monde réel dans un programme. Cependant, en réalité j'ai un chien (avec deux oreilles, quatre pattes et un collier) et un compte bancaire (avec un gérant, des commis et une pause déjeuner), et dans le programme - WalkManager, AccountFactory... eh bien, vous obtenez l'idée. Et le fait n'est pas que le programme ait des classes auxiliaires qui ne reflètent pas les objets du monde réel. Le fait est que contrôler les changements de flux. Walking Manager me prive de la joie de promener mon chien et je reçois de l'argent sur un compte bancaire sans âme (hé, où est cette jolie fille sur laquelle j'ai changé de l'argent la semaine dernière ?).

Je suis peut-être snob, mais j'étais beaucoup plus heureux lorsque les données de l'ordinateur n'étaient que des données, même si elles décrivaient mon chien ou un compte bancaire. Je pouvais faire ce qui me convenait avec les données, sans égard au monde réel.

Je ne sais pas non plus comment décomposer correctement les fonctionnalités. En Python ou C++, si j'avais besoin d'une petite fonction pour convertir une chaîne en nombre, je l'écrivais simplement à la fin du fichier. En Java ou C#, je suis obligé de le mettre dans une classe StringUtils distincte. Dans les langages pré-OO, je pouvais déclarer un wrapper ad hoc pour renvoyer deux valeurs d'une fonction (le montant retiré et le solde du compte). Dans les langages POO, je devrai créer une classe de résultat de transaction à part entière. Et pour une nouvelle personne sur le projet (ou même pour moi-même une semaine plus tard), ce cours semblera tout aussi important et fondamental dans l'architecture du système. 150 fichiers, et tous tout aussi importants et fondamentaux – oh oui, une architecture transparente, de merveilleux niveaux d'abstraction.

Je ne sais pas comment écrire des programmes efficaces. Les programmes efficaces utilisent peu de mémoire - sinon le garbage collector ralentira constamment l'exécution. Mais pour réaliser l’opération la plus simple dans les langages orientés objet, il faut créer une douzaine d’objets. Pour faire une requête HTTP, je dois créer un objet de type URL, puis un objet de type HttpConnection, puis un objet de type Request... eh bien, vous voyez l'idée. En programmation procédurale, j'appellerais simplement plusieurs procédures en leur passant une structure créée sur la pile. Très probablement, un seul objet serait créé en mémoire : pour stocker le résultat. En POO, je dois encombrer la mémoire tout le temps.

Peut-être que la POO est un paradigme vraiment beau et élégant. Peut-être que je ne suis tout simplement pas assez intelligent pour le comprendre. Il y a probablement quelqu'un qui peut créer un très bon programme dans un langage orienté objet. Eh bien, je ne peux que les envier.