Quels problèmes de programmation procédurale la POO résout-elle dans la pratique?


17

J'ai étudié le livre "C ++ Demystified" . Maintenant, j'ai commencé à lire "Programmation orientée objet dans Turbo C ++ première édition (1ère édition)" par Robert Lafore. Je n'ai aucune connaissance en programmation qui dépasse ces livres. Ce livre est peut-être dépassé car il a 20 ans. J'ai la dernière édition, j'utilise l'ancienne parce que je l'aime, principalement j'étudie simplement les concepts de base de la POO utilisés en C ++ à travers la première édition du livre de Lafore.

Le livre de Lafore souligne que "OOP" n'est utile que pour des programmes plus vastes et complexes . Il est dit dans chaque livre OOP (également dans le livre de Lafore) que le paradigme procédural est sujet à des erreurs, par exemple les données globales comme facilement vulnérables par les fonctions. On dit que le programmeur peut faire des erreurs honnêtes dans les langages procéduraux, par exemple en créant une fonction qui corrompt accidentellement les données.

Honnêtement, je poste ma question parce que je ne comprends pas l'explication donnée dans ce livre: Programmation orientée objet en C ++ (4e édition) Je ne saisis pas ces déclarations écrites dans le livre de Lafore:

La programmation orientée objet a été développée parce que des limites ont été découvertes dans les approches précédentes de la programmation .... Alors que les programmes deviennent de plus en plus grands et plus complexes, même l'approche de programmation structurée commence à montrer des signes de tension ... .... Analyser les raisons de ces échecs révèlent qu'il existe des faiblesses dans le paradigme procédural lui-même. Peu importe la façon dont l'approche de programmation structurée est mise en œuvre, les grands programmes deviennent excessivement complexes ... ... Il y a deux problèmes liés. Premièrement, les fonctions ont un accès illimité aux données globales. Deuxièmement, les fonctions et les données non liées, à la base du paradigme procédural, fournissent un mauvais modèle du monde réel ...

J'ai étudié le livre "dysmystified C ++" de Jeff Kent, j'aime beaucoup ce livre, dans ce livre la programmation principalement procédurale est expliquée. Je ne comprends pas pourquoi la programmation procédurale (structurée) est faible!

Le livre de Lafore explique très bien le concept avec de bons exemples. J'ai également saisi une intuition en lisant le livre de Lafore que la POO est meilleure que la programmation procédurale, mais je suis curieux de savoir comment, en pratique, la programmation procédurale est plus faible que la POO.

Je veux voir moi-même quels sont les problèmes pratiques que l'on pourrait rencontrer dans la programmation procédurale, comment le POO facilitera la programmation. Je pense que j'obtiendrai ma réponse simplement en lisant le livre de Lafore de manière contemplative, mais je veux voir de mes propres yeux les problèmes dans le code de procédure, je veux voir comment le code de style OOP d'un programme supprime les erreurs précédentes qui se produiraient si le le même programme devait être écrit en utilisant le paradigme procédural.

Il existe de nombreuses fonctionnalités de la POO et je comprends qu'il n'est pas possible pour quelqu'un de m'expliquer comment toutes ces fonctionnalités suppriment les erreurs précédentes qui se produiraient en écrivant le code dans un style procédural.

Voici donc ma question:

À quelles limitations de la programmation procédurale la POO traite-t-elle et comment supprime-t-elle efficacement ces limitations dans la pratique?

En particulier, existe-t-il des exemples de programmes qui sont difficiles à concevoir en utilisant le paradigme procédural mais qui sont facilement conçus en utilisant la POO?

PS: Croix publiée sur: /programming//q/22510004/3429430


3
La distinction entre programmation procédurale et programmation orientée objet est dans une certaine mesure une question de présentation et d'accentuation. La plupart des langues qui se présentent comme orientées objet sont également procédurales - les termes examinent différents aspects de la langue.
Gilles 'SO- arrête d'être méchant'

2
J'ai rouvert la question maintenant; Voyons comment cela se passe. Gardez à l'esprit que tout traitement qui présente la POO est le Saint Graal, qui résout tous les problèmes, le messie des langages de programmation est un non-sens. Il y a des avantages et des inconvénients. Vous demandez des avantages, et c'est une bonne question; ne vous attendez pas à plus.
Raphael

L'idée de concevoir une interface graphique de bureau (moderne) sans POO et certaines techniques qui y ont grandi (par exemple le modèle d'événement / auditeur) me fait peur. Cela ne veut pas dire que cela ne peut pas être fait (c'est certainement possible ), cependant. De plus, si vous voulez voir l'échec de PP au travail, regardez PHP et comparez l'API avec, disons, Ruby.
Raphael

1
"Peu importe la façon dont l'approche de programmation structurée est mise en œuvre, les grands programmes deviennent excessivement complexes ...", ce qui est tout aussi vrai de la POO, mais il gère mieux la complexité s'il est appliqué de la manière prescrite par les développeurs ... et le fait cela en grande partie à travers des limites / restrictions de portée meilleures / plus nettes, c'est-à-dire une sorte de système de compartimentation ... aka APIE: Abstraction, Polymorphism, Inheritance, Encapsulation
vzn

Réponses:


9

Dans un langage procédural, vous ne pouvez pas nécessairement exprimer les restrictions requises pour prouver que l'appelant utilise un module de manière prise en charge. En l'absence de restriction vérifiable par le compilateur, vous devez écrire de la documentation et espérer qu'elle sera suivie, et utiliser des tests unitaires pour démontrer les utilisations prévues.

La déclaration des types est la restriction déclarative la plus évidente (c'est-à-dire: "prouver que x est un flottant"). Forcer les mutations de données à passer par une fonction connue pour être conçue pour ces données en est une autre. L'application du protocole (méthode invoquant l'ordre) est une autre restriction partiellement prise en charge, à savoir: "constructeur -> autres méthodes * -> destructeur".

Il existe également de réels avantages (et quelques inconvénients) lorsque le compilateur connaît le modèle. Le typage statique avec des types polymorphes est un peu un problème lorsque vous émulez l'encapsulation de données à partir d'un langage procédural. Par exemple:

le type x1 est un sous-type de x, t1 est un sous-type de t

C'est une façon d'encapsuler des données dans un langage procédural pour avoir un type t avec les méthodes f et g, et une sous-classe t1 qui fait de même:

t_f (t, x, y, z, ...), t_g (t, x, y, ...) t1_f (t1, x, y, z, ...)

Pour utiliser ce code tel quel, vous devez faire une vérification de type et activer le type de t avant de décider du type de f que vous invoquerez. Vous pouvez contourner cela comme ceci:

tapez t {d: données f: fonction g: fonction}

Pour que vous appeliez à la place tf (x, y, z), où une vérification de type et un commutateur pour trouver la méthode sont désormais remplacés par le simple fait d'avoir chaque pointeur de méthode de stockage d'instance explicitement. Maintenant, si vous avez un grand nombre de fonctions par type, c'est une représentation inutile. Vous pourriez alors utiliser une autre stratégie comme avoir t pointe vers une variable m qui contient toutes les fonctions membres. Si cette fonctionnalité fait partie du langage, vous pouvez laisser le compilateur comprendre comment gérer une représentation efficace de ce modèle.

Mais l'encapsulation des données est la reconnaissance que l'état mutable est mauvais. La solution orientée objet est de le cacher derrière les méthodes. Idéalement, toutes les méthodes d'un objet devraient avoir un ordre d'appel bien défini (c'est-à-dire: constructeur -> ouvrir -> [lire | écrire] -> fermer -> détruire); qui est parfois appelé «protocole» (recherche: «Microsoft Singularity»). Mais au-delà de la construction et de la destruction, ces exigences ne font généralement pas partie du système de type - ou sont bien documentées. En ce sens, les objets sont des instances simultanées de machines d'état qui sont transférées par des appels de méthode; de sorte que vous pourriez avoir plusieurs instances et les utiliser de manière arbitrairement entrelacée.

Mais en reconnaissant que l'état partagé mutable est mauvais, on peut noter que l'orientation d'objet peut créer un problème de concurrence car la structure de données d'objet est un état mutable auquel de nombreux objets ont une référence. La plupart des langages orientés objet s'exécutent sur le thread de l'appelant, ce qui signifie qu'il y a des conditions de concurrence dans les appels de méthode; encore moins dans des séquences non atomiques d'appels de fonction. Comme alternative, chaque objet pourrait obtenir des messages asynchrones dans une file d'attente et les entretenir tous sur le thread de l'objet (avec ses méthodes privées), et répondre à l'appelant en lui envoyant des messages.

Comparez les appels de méthode Java dans un contexte multithread avec les processus Erlang qui s'envoient des messages (qui ne font référence qu'à des valeurs immuables).

L'orientation non restreinte des objets en combinaison avec le parallélisme est un problème dû au verrouillage. Il existe des techniques allant de la mémoire transactionnelle logicielle (c'est-à-dire: les transactions ACID sur des objets en mémoire similaires aux bases de données) à l'utilisation d'une approche de «partage de mémoire en communiquant (données immuables)» (hybride de programmation fonctionnelle).

À mon avis, la littérature sur l'orientation d'objet se concentre trop sur l'héritage et pas assez sur le protocole (ordre d'invocation des méthodes vérifiables, conditions préalables, postconditions, etc.). L'entrée qu'un objet consomme doit généralement avoir une grammaire bien définie, exprimable en tant que type.


Voulez-vous dire que dans les langages OO, le compilateur peut vérifier si les méthodes sont utilisées dans l'ordre prescrit, ou d'autres restrictions sur l'utilisation des modules? Pourquoi "l'encapsulation des données [...] reconnaît-elle que l'état mutable est mauvais"? Lorsque vous parlez de polymorphisme, supposez-vous que vous utilisez un langage OO?
babou

Dans OO, la capacité la plus importante est de pouvoir masquer la structure de données (c'est-à-dire: exiger que les références soient comme celle-ci.x) pour prouver que tous les accès passent par des méthodes. Dans un langage OO typé statiquement, vous déclarez encore plus de restrictions (basées sur les types). La note sur l'ordre des méthodes dit simplement que OO oblige le constructeur à être appelé en premier et le destructeur en dernier; qui est la première étape pour interdire structurellement les mauvaises commandes d'appel. Les épreuves faciles sont un objectif important dans la conception d'un langage.
Rob

8

La programmation procédurale / fonctionnelle n'est en aucun cas plus faible que la POO , même sans entrer dans les arguments de Turing (mon langage a le pouvoir de Turing et peut faire n'importe quoi d'autre), ce qui ne veut pas dire grand-chose. En fait, les techniques orientées objet ont d'abord été expérimentées dans des langages qui ne les intégraient pas. En ce sens, la programmation OO n'est qu'un style spécifique de programmation procédurale . Mais il aide à faire respecter des disciplines spécifiques, telles que la modularité, l'abstraction et la dissimulation d'informations qui sont essentielles pour la compréhension et la maintenance du programme.

Certains paradigmes de programmation découlent d'une vision théorique du calcul. Un langage comme Lisp a évolué à partir du lambda-calcul et de l'idée de méta-circularité des langages (similaire à la réflexivité dans le langage naturel). Les clauses Horn ont engendré Prolog et la programmation par contraintes. La famille Algol doit également au lambda-calcul, mais sans réflexivité intégrée.

Lisp est un exemple intéressant, car il a été le banc d'essai de nombreuses innovations de langage de programmation, qui sont traçables à son double héritage génétique.

Mais les langues évoluent alors, souvent sous de nouveaux noms. Un facteur d'évolution majeur est la pratique de la programmation. Les utilisateurs identifient les pratiques de programmation qui améliorent les propriétés des programmes telles que la lisibilité, la maintenabilité, la prouvabilité de l'exactitude. Ensuite, ils essaient d'ajouter aux langages des fonctionnalités ou des contraintes qui prendront en charge et parfois appliqueront ces pratiques afin d'améliorer la qualité des programmes.

Cela signifie que ces pratiques sont déjà possibles dans les anciens langages de programmation, mais il faut de la compréhension et de la discipline pour les utiliser. Leur intégration dans de nouveaux langages en tant que concepts principaux avec une syntaxe spécifique rend ces pratiques plus faciles à utiliser et à comprendre facilement, en particulier pour les utilisateurs les moins sophistiqués (c'est-à-dire la grande majorité). Cela facilite également la vie des utilisateurs avertis.

D'une certaine manière, c'est pour concevoir un langage ce qu'est un sous-programme / fonction / procédure pour un programme. Une fois le concept utile identifié, il reçoit un nom (éventuellement) et une syntaxe, de sorte qu'il peut être facilement utilisé dans tous les programmes développés avec ce langage. Et en cas de succès, il sera également intégré dans les futures langues.

Exemple: recréer l'orientation d'un objet

J'essaie maintenant d'illustrer cela sur un exemple (qui pourrait certainement être peaufiné davantage, avec le temps). Le but de l'exemple n'est pas de montrer qu'un programme orienté objet peut être écrit dans un style de programmation procédurale, éventuellement au détriment de la lisibilité et de la maintenabilité. Je vais plutôt essayer de montrer que certains langages sans installations OO peuvent en fait utiliser des fonctions d'ordre supérieur et une structure de données pour créer réellement les moyens de mimer efficacement l'orientation d'objet , afin de bénéficier de ses qualités en matière d'organisation de programme, notamment la modularité, l'abstraction et le masquage d'informations. .

Comme je l'ai dit, Lisp a été le banc d'essai de nombreuses évolutions linguistiques, y compris le paradigme OO (bien que ce qui pourrait être considéré comme la première langue OO soit Simula 67, dans la famille Algol). Lisp est très simple et le code de son interpréteur de base est inférieur à une page. Mais vous pouvez faire de la programmation OO en Lisp. Tout ce dont vous avez besoin, c'est de fonctions d'ordre supérieur.

Je n'utiliserai pas la syntaxe Lisp ésotérique, mais plutôt un pseudo-code, pour simplifier la présentation. Et je considérerai un problème essentiel simple: le masquage de l'information et la modularité . Définir une classe d'objets tout en empêchant l'utilisateur d'accéder (la plupart) à l'implémentation.

Supposons que je veuille créer une classe appelée vecteur, représentant des vecteurs bidimensionnels, avec des méthodes comprenant: l'addition de vecteur, la taille du vecteur et le parallélisme.

function vectorrec () {  
  function createrec(x,y) { return [x,y] }  
  function xcoordrec(v) { return v[0] }  
  function ycoordrec(v) { return v[1] }  
  function plusrec (u,v) { return [u[0]+v[0], u[1]+v[1]] }  
  function sizerec(v) { return sqrt(v[0]*v[0]+v[1]*v[1]) }  
  function parallelrec(u,v) { return u[0]*v[1]==u[1]*v[0]] }  
  return [createrec, xcoordrec, ycoordrec, plusrec, sizerec, parallelrec]  
  }  

Ensuite, je peux attribuer le vecteur créé aux noms de fonction réels à utiliser.

[vecteur, xcoord, ycoord, vplus, vsize, vparallel] = vectorclass ()

Pourquoi être si compliqué? Parce que je peux définir dans la fonction vectorrec des constructions intermédiaires que je ne veux pas être visible pour le reste du programme, afin de conserver la modularité.

On peut faire une autre collection en coordonnées polaires

function vectorpol () {  
  ...  
  function pluspol (u,v) { ... }  
  function sizepol (v) { return v[0] }  
  ...  
  return [createpol, xcoordpol, ycoordpol, pluspol, sizepol, parallelpol]  
  }  

Mais je peux vouloir utiliser indifféremment les deux implémentations. Une façon de le faire consiste à ajouter un composant de type à toutes les valeurs et à définir toutes les fonctions ci-dessus dans le même environnement: Ensuite, je peux définir chacune des fonctions renvoyées afin qu'il teste d'abord le type de coordonnées, puis applique la fonction spécifique pour ça.

function vector () {  
    ...  
    function plusrec (u,v) { ... }  
    ...  
    function pluspol (u,v) { ... }  
    ...  
    function plus (u,v) { if u[2]='rec' and v[2]='rec'  
                            then return plusrec (u,v) ... }  

    return [ ..., plus, ...]  
    }

Ce que j'ai gagné: les fonctions spécifiques restent invisibles (en raison de la portée des identifiants locaux), et le reste du programme ne peut utiliser que les plus abstraites retournées par l'appel à vectorclass.

Une objection est que je pourrais définir directement chacune des fonctions abstraites du programme et laisser à l'intérieur la définition des fonctions dépendantes du type de coordonnées. Il serait également caché. C'est vrai, mais alors le code pour chaque type de coordonnées serait coupé en petits morceaux répartis sur le programme, ce qui est moins réductible et maintenable.

En fait, je n'ai même pas besoin de leur donner un nom, et je pourrais simplement conserver les valeurs fonctionnelles anonymes dans une structure de données indexée par le type et une chaîne représentant le nom de la fonction. Cette structure étant locale au vecteur de fonction serait invisible du reste du programme.

Pour simplifier l'utilisation, au lieu de renvoyer une liste de fonctions, je peux renvoyer une seule fonction appelée apply en prenant comme argument une valeur de type explicite et une chaîne, et appliquer la fonction avec le type et le nom appropriés. Cela ressemble beaucoup à l'appel d'une méthode pour une classe OO.

Je m'arrêterai ici, dans cette reconstitution d'une installation orientée objet.

Ce que j'ai essayé de faire, c'est de montrer qu'il n'est pas trop difficile de créer une orientation d'objet utilisable dans un langage suffisamment puissant, y compris l'héritage et d'autres fonctionnalités de ce type. La métacircularité de l'interpréteur peut aider, mais surtout au niveau syntaxique, ce qui est encore loin d'être négligeable.

Les premiers utilisateurs de l'orientation objet ont expérimenté les concepts de cette façon. Et cela est généralement vrai pour de nombreuses améliorations apportées aux langages de programmation. Bien entendu, l'analyse théorique a également un rôle à jouer et a permis de comprendre ou d'affiner ces concepts.

Mais l'idée que les langues qui n'ont pas de fonctionnalités OO sont vouées à l'échec dans certains projets est tout simplement injustifiée. Au besoin, ils peuvent imiter la mise en œuvre de ces fonctionnalités de manière assez efficace. De nombreux langages ont le pouvoir syntaxique et sémantique de faire une orientation d'objet assez efficacement, même lorsqu'il n'est pas intégré. Et c'est plus qu'un argument de Turing.

La POO ne traite pas des limitations des autres langages, mais elle prend en charge ou applique les méthodologies de programmation qui aident à écrire de meilleurs programmes, aidant ainsi les utilisateurs moins expérimentés à suivre les bonnes pratiques que les programmeurs plus avancés utilisent et développent sans ce support.

Je crois qu'un bon livre pour comprendre tout cela pourrait être Abelson & Sussman: structure et interprétation des programmes informatiques .


8

Un peu d'histoire s'impose, je pense.

L'époque du milieu des années 60 au milieu des années 70 est connue aujourd'hui sous le nom de «crise des logiciels». Je ne peux pas dire mieux que Dijkstra dans sa conférence du prix Turing de 1972:

La principale cause de la crise des logiciels est que les machines sont devenues plusieurs ordres de grandeur plus puissantes! Pour le dire franchement: tant qu'il n'y avait pas de machines, la programmation n'était pas du tout un problème; quand nous avions quelques ordinateurs faibles, la programmation est devenue un problème léger, et maintenant nous avons des ordinateurs gigantesques, la programmation est devenue un problème tout aussi gigantesque.

C'était l'époque des premiers ordinateurs 32 bits, des premiers vrais multiprocesseurs et des premiers ordinateurs embarqués, et il était clair pour les chercheurs que ceux-ci allaient être importants pour la programmation à l'avenir. C'était une époque de l'histoire où la demande des clients dépassait pour la première fois la capacité des programmeurs.

Sans surprise, ce fut une période remarquablement fertile dans la recherche en programmation. Avant le milieu des années 60, nous avions bien LISP et AP / L, mais les langues "principales" étaient fondamentalement procédurales: FORTRAN, ALGOL, COBOL, PL / I et ainsi de suite. Du milieu des années 1960 au milieu des années 1970, nous avons obtenu Logo, Pascal, C, Forth, Smalltalk, Prolog, ML et Modula, et cela ne compte pas les DSL comme SQL et ses prédécesseurs.

C'était aussi une époque de l'histoire où de nombreuses techniques clés pour la mise en œuvre de langages de programmation étaient en cours de développement. Au cours de cette période, nous avons obtenu l'analyse LR, l'analyse du flux de données, l'élimination de la sous-expression commune et la première reconnaissance que certains problèmes de compilateur (par exemple l'allocation de registres) étaient NP-difficiles, et essayant de les résoudre en tant que tels.

C'est dans ce contexte que la POO a vu le jour. Donc, au début des années 1970, répondez à votre question sur les problèmes que la POO résout dans la pratique, la première réponse est qu'elle semblait résoudre de nombreux problèmes (contemporains et anticipés) auxquels étaient confrontés les programmeurs à cette période de l'histoire. Cependant, ce n'est pas le moment où OO est devenu courant. Nous y reviendrons assez tôt.

Quand Alan Kay a inventé le terme "orienté objet", l'image qu'il avait en tête était que les systèmes logiciels seraient structurés comme un système biologique. Vous auriez quelque chose comme des cellules individuelles ("objets") qui interagissent les unes avec les autres en envoyant quelque chose d'analogue aux signaux chimiques ("messages"). Vous ne pouviez pas (ou, du moins, ne le feriez pas) scruter à l'intérieur d'une cellule; vous interagiriez uniquement avec lui via les voies de signalisation. De plus, vous pourriez avoir plus d'une cellule de chaque type si vous en aviez besoin.

Vous pouvez voir qu'il y a quelques thèmes importants ici: le concept d'un protocole de signalisation bien défini (dans la terminologie moderne, une interface), le concept de cacher l'implémentation de l'extérieur (dans la terminologie moderne, la confidentialité) et le concept de avoir plusieurs «choses» du même type qui traînent en même temps (dans la terminologie moderne, l'instanciation).

Une chose que vous remarquerez peut-être est manquante, et c'est l'héritage, et il y a une raison à cela.

La programmation orientée objet est une notion abstraite, et la notion abstraite peut être implémentée de différentes manières dans différents langages de programmation. Le concept abstrait d'une "méthode", par exemple, pourrait être implémenté en C en utilisant des pointeurs de fonction, et en C ++ en utilisant des fonctions membres, et en Smalltalk en utilisant des méthodes (ce qui ne devrait pas être surprenant, car Smalltalk implémente le concept abstrait de manière assez directe). C'est ce que les gens veulent dire lorsqu'ils soulignent (à juste titre) que vous pouvez "faire" la POO dans (presque) n'importe quelle langue.

L'héritage, d'autre part, est une fonctionnalité concrète du langage de programmation. L'héritage peut être utile pour la mise en œuvre de systèmes OOP. Ou du moins, ce fut le cas jusqu'au début des années 1990.

La période allant du milieu des années 80 au milieu des années 90 a également été une période de l'histoire où les choses changeaient. Pendant cette période, nous avons assisté à l'essor de l'ordinateur 32 bits omniprésent et bon marché, de sorte que les entreprises et de nombreux foyers pouvaient se permettre d'installer sur chaque bureau un ordinateur presque aussi puissant que les ordinateurs centraux les plus bas de la journée. C'était aussi l'apogée de Ce fut aussi l'ère de la montée en puissance de l'interface graphique moderne et du système d'exploitation en réseau.

C'est dans ce contexte qu'est née l'analyse et la conception orientées objet.

L'influence d'OOAD, le travail des "trois Amigos" (Booch, Rumbar et Jacobson) et d'autres (par exemple la méthode Shlaer-Mellor, la conception axée sur la responsabilité, etc.) ne peuvent pas être sous-estimés. C'est la raison pour laquelle la plupart des nouveaux langages développés depuis le début des années 1990 (du moins, la plupart de ceux dont vous avez entendu parler) ont des objets de style Simula.

Ainsi, la réponse des années 1990 à votre question est qu'elle prend en charge la meilleure solution (à l'époque) pour l'analyse de domaine et la méthodologie de conception.

Depuis lors, parce que nous avions un marteau, nous avons appliqué la POO à presque tous les problèmes qui sont survenus depuis. OOAD et le modèle d'objet qu'il a utilisé ont encouragé et activé le développement agile et piloté par les tests, les clusters et autres systèmes distribués, etc.

Les interfaces graphiques modernes et tous les systèmes d'exploitation conçus au cours des 20 dernières années ont tendance à fournir leurs services en tant qu'objets, de sorte que tout nouveau langage de programmation pratique a besoin, à tout le moins, d'un moyen de se lier aux systèmes que nous utilisons aujourd'hui.

La réponse moderne est donc: elle résout le problème de l'interface avec le monde moderne. Le monde moderne est construit sur OOP pour la même raison que le monde de 1880 a été construit sur la vapeur: nous le comprenons, nous pouvons le contrôler, et il fait assez bien le travail.

Cela ne veut pas dire que la recherche s'arrête ici, bien sûr, mais cela indique fortement que toute nouvelle technologie aura besoin d'OO comme cas limite. Vous n'êtes pas obligé d'être OO, mais vous ne pouvez pas être fondamentalement incompatible avec lui.


Un côté que je ne voulais pas mettre dans le texte principal est que les GUI et les POO WIMP semblent être un ajustement extrêmement naturel. Beaucoup de mauvaises choses peuvent être dites des hiérarchies d'héritage profondes, mais c'est une situation (probablement la SEULE situation) où cela semble avoir un certain sens.
Pseudonyme

1
La POO est apparue en premier dans Simula-67 (simulation), dans l'organisation interne des systèmes d'exploitation (l'idée de «classe de périphérique» sous Unix est essentiellement une classe dont les pilotes héritent). "Sur les critères à utiliser pour décomposer les systèmes en modules" de Parnas , CACM 15:12 (1972), pp. 1052-1058, langage Modula de Wirth des années 70, les "types de données abstraits" sont tous des précurseurs autre.
vonbrand

C'est vrai, mais je maintiens que la POO n'était pas considérée comme une "solution à un problème de programmation procédurale" jusqu'au milieu des années 70. Définir "OOP" est notoirement difficile. L'utilisation originale par Alan Kay du terme n'est pas d'accord avec le modèle de Simula, et malheureusement le monde s'est normalisé sur le modèle de Simula. Certains modèles d'objets ont une interprétation de Curry-Howard, mais pas celle de Simula. Stepanov avait probablement raison lorsqu'il a noté que l'hérédité n'était pas saine.
Pseudonyme

6

Aucun, vraiment. La POO ne résout pas vraiment un problème à proprement parler; il n'y a rien que vous puissiez faire avec un système orienté objet que vous ne pourriez pas faire avec un système non orienté objet - en effet, il n'y a rien que vous puissiez faire avec l'un ou l'autre qui ne pourrait pas être fait avec une machine Turing. Tout cela se transforme finalement en code machine, et ASM n'est certainement pas orienté objet.

Ce que le paradigme de POO fait pour vous, c'est qu'il facilite l'organisation des variables et des fonctions et vous permet de les déplacer plus facilement ensemble.

Disons que je veux écrire un jeu de cartes en Python. Comment pourrais-je représenter les cartes?

Si je ne connaissais pas la POO, je pourrais le faire de cette façon:

cards=["1S","2S","3S","4S","5S","6S","7S","8S","9S","10S","JS","QS","KS","1H","2H",...,"10C","JC","QC","KC"]

J'écrirais probablement du code pour générer ces cartes au lieu de simplement les écrire à la main, mais vous obtenez le point. "1S" représente le 1 de pique, "JD" représente le Jack of Diamonds, et ainsi de suite. J'aurais également besoin d'un code pour le Joker, mais nous allons simplement prétendre qu'il n'y a pas de Joker pour l'instant.

Maintenant, quand je veux mélanger le jeu, il me suffit de "mélanger" la liste. Ensuite, pour retirer une carte du haut du jeu, je retire l'entrée du haut de la liste, en me donnant la chaîne. Facile.

Maintenant, si je veux savoir avec quelle carte je travaille pour l'afficher sur le lecteur, j'aurais besoin d'une fonction comme celle-ci:

def card_code_to_name(code):
    suit=code[1]

    if suit=="S":
        suit="Spades"
    elif suit=="H"
        suit="Hearts"
    elif suit=="D"
        suit="Diamonds"
    elif suit=="C"
        suit="Clubs"

    value=code[0]

    if value=="J":
        value="Jack"
    elif value="Q":
        value="Queen"
    elif value="K"
        value="King"

    return value+" of "+suit

Un peu grand, long et inefficace, mais cela fonctionne (et est très impythonique, mais c'est à côté du point ici).

Maintenant, que se passe-t-il si je veux que les cartes puissent se déplacer sur l'écran? Je dois en quelque sorte stocker leur position. Je pourrais l'ajouter à la fin de leur code de carte, mais cela pourrait être un peu compliqué. Au lieu de cela, faisons une autre liste de l'emplacement de chaque carte:

cardpositions=( (1,1), (2,1), (3,1) ...)

Ensuite, j'écris mon code afin que l'index de la position de chaque carte dans la liste soit le même que l'index de la carte elle-même dans le jeu.

Ou du moins, ça devrait l'être. A moins que je ne fasse une erreur. Ce que je pourrais très bien, car mon code devra être assez complexe pour gérer cette configuration. Lorsque je veux mélanger les cartes, je dois mélanger les positions dans le même ordre. Que se passe-t-il si je retire entièrement une carte du jeu? Je devrai également retirer sa position et la mettre ailleurs.

Et si je veux stocker encore plus d'informations sur les cartes? Que faire si je veux enregistrer si chaque carte est retournée? Et si je veux un moteur physique quelconque et que je dois également connaître la vitesse des cartes? J'aurai besoin d'une autre liste entièrement pour stocker les graphiques de chaque carte! Et pour tous ces points de données, j'aurai besoin d'un code séparé pour les garder tous correctement organisés afin que chaque carte mappe d'une manière ou d'une autre à toutes ses données!

Essayons maintenant ceci de la façon OOP.

Au lieu d'une liste de codes, définissons une classe Card et construisons une liste d'objets Card à partir de celle-ci.

class Card:

    def __init__(self,value,suit,pos,sprite,flipped=False):
        self.value=value
        self.suit=suit
        self.pos=pos
        self.sprite=sprite
        self.flipped=flipped

    def __str__(self):
        return self.value+" of "+self.suit

    def flip(self):
        if self.flipped:
            self.flipped=False
            self.sprite=load_card_sprite(value, suit)
        else:
            self.flipped=True
            self.sprite=load_card_back_sprite()

deck=[]
for suit in ("Spades","Hearts","Diamonds","Clubs"):
    for value in ("1","2","3","4","5","6","7","8","9","10","Jack","Queen","King"):
        sprite=load_card_sprite(value, suit)
        thecard=Card(value,suit,(0,0),sprite)
        deck.append(thecard)

Maintenant, tout à coup, tout est beaucoup plus simple. Si je veux déplacer la carte, je n'ai pas besoin de savoir où elle se trouve dans le jeu, puis utilisez-la pour obtenir sa position dans le tableau de positions. Je dois juste direthecard.pos=newpos . Lorsque je retire la carte de la liste du deck principal, je n'ai pas à créer une nouvelle liste pour stocker toutes ces autres données; lorsque l'objet carte se déplace, toutes ses propriétés se déplacent avec lui. Et si je veux une carte qui se comporte différemment lorsqu'elle est retournée, je n'ai pas à modifier la fonction de retournement dans mon code principal afin qu'elle détecte ces cartes et fasse ce comportement différent; Je dois juste sous-classer la carte et modifier la fonction flip () sur la sous-classe.

Mais rien de ce que j'ai fait là-bas n'aurait pu être fait sans OO. C'est juste qu'avec un langage orienté objet, le langage fait beaucoup de travail pour garder les choses ensemble pour vous, ce qui signifie que vous avez beaucoup moins de chance de faire des erreurs, et votre code est plus court et plus facile à lire et à écrire.

Ou, pour résumer encore plus, OO vous permet d'écrire des programmes plus simples qui font le même travail que des programmes plus complexes en cachant une grande partie des complexités courantes de la gestion des données derrière un voile d'abstraction.


1
Si la seule chose que vous retirez de la POO est la "gestion de la mémoire", je ne pense pas que vous l'ayez très bien comprise. Il y a toute une philosophie de conception et une généreuse bouteille de "correct par conception"! De plus, la gestion de la mémoire n'est certainement rien d'inhérent à l'orientation d'objet (C ++?), Même si le besoin en devient plus prononcé.
Raphael

Bien sûr, mais c'est la version d'une phrase. Et j'ai également utilisé le terme d'une manière plutôt non standard. Il serait peut-être préférable de dire «gestion des informations» que «gestion de la mémoire».
Schilcote

Existe-t-il des langages non OOP qui permettraient à une fonction de prendre un pointeur sur quelque chose, ainsi qu'un pointeur sur une fonction dont le premier paramètre est un pointeur sur ce même genre de chose, et que le compilateur valide que la fonction est appropriée pour le pointeur transmis?
supercat

3

Ayant écrit du C intégré pendant quelques années en gérant des choses comme les périphériques, les ports série et les paquets de communication entre les ports série, les ports réseau et les serveurs; Je me suis retrouvé, un ingénieur électricien formé avec une expérience de programmation procédurale limitée, concoctant mes propres abstractions du matériel qui a fini par se manifester dans ce que j'ai réalisé plus tard comme ce que les gens normaux appellent la `` programmation orientée objet ''.

Lorsque je me suis déplacé vers le côté serveur, j'ai été exposé à une usine qui a configuré la représentation des objets de chaque périphérique dans la mémoire lors de l'instanciation. Je n'ai pas compris les mots ou ce qui se passait au début - je savais juste que je suis allé dans le fichier nommé ainsi et ainsi j'ai écrit du code. Plus tard, je me suis retrouvé, encore une fois, reconnaissant enfin la valeur de la POO.

Personnellement, je pense que c'est la seule façon d'enseigner l'orientation des objets. J'ai eu un cours d'introduction à la POO (Java) ma première année et c'était complètement au-dessus de ma tête. Les descriptions de POO construites sur la classification des chaton-> chat-> mammifère-> vivant-> chose ou feuille-> branche-> arbre-> jardin sont, à mon humble avis, des méthodologies absolument ridicules parce que personne ne va jamais essayer de résoudre ces problèmes, si vous pouviez même les appeler des problèmes ...

Je pense qu'il est plus facile de répondre à votre question si vous la regardez en termes moins absolus - pas `` qu'est-ce que cela résout '', mais plutôt du point de vue `` voici un problème, et voici comment cela le rend plus facile ''. Dans mon cas particulier des ports série, nous avions un tas de #ifdefs à la compilation qui ajoutaient et supprimaient du code qui ouvrait et fermait statiquement les ports série. Les fonctions d'ouverture de port étaient appelées partout et pouvaient être situées n'importe où dans les 100 000 lignes de code OS que nous avions, et l'EDI n'a pas grisé ce qui n'était pas défini - vous deviez le retrouver manuellement, et portez-le dans votre tête. Inévitablement, vous pourriez avoir plusieurs tâches à essayer d'ouvrir un port série donné en attendant leur appareil à l'autre extrémité, puis aucun du code que vous venez d'écrire ne fonctionne, et vous ne pouvez pas comprendre pourquoi.

L'abstraction était, bien que toujours en C, une «classe» de port série (enfin, juste un type de données de structure) que nous avions un tableau de - un pour chaque port série - et au lieu d'avoir [l'équivalent DMA dans les ports série] "OpenSerialPortA" "SetBaudRate" etc. fonctions appelées directement sur le matériel de la tâche, nous avons appelé une fonction d'assistance à laquelle vous avez passé tous les paramètres de communication (baud, parité, etc.), qui a d'abord vérifié le tableau de structure pour voir si cela le port avait déjà été ouvert - si oui, par quelle tâche, qu'il vous dirait en tant que printf de débogage, afin que vous puissiez immédiatement accéder à la section de code que vous deviez désactiver - et sinon, il a ensuite procédé à la définition tous les paramètres via leurs fonctions d'assemblage HAL, et enfin ouvert le port.

Bien sûr, la POO comporte également des dangers. Quand j'avais finalement nettoyé cette base de code et rendu tout propre et net - l'écriture de nouveaux pilotes pour cette gamme de produits était finalement une science calculable et prévisible, mon manager a fini par utiliser le produit spécifiquement parce que c'était un projet de moins dont il aurait besoin à gérer, et il était borderline amovible middle-management. Ce qui m'a beaucoup retiré / j'ai trouvé très décourageant alors j'ai quitté mon emploi. LOL.


1
Salut! Cela ressemble plus à une histoire personnelle qu'à une réponse à la question. D'après votre exemple, je vois que vous avez réécrit un code horrible dans un style orienté objet, ce qui l'a rendu meilleur. Mais on ne sait pas si l'amélioration a beaucoup à voir avec l'orientation des objets ou si c'est simplement parce que vous étiez un programmeur plus compétent à ce moment-là. Par exemple, une grande partie de votre problème semble provenir du code dispersé au gré de l'endroit. Cela aurait pu être résolu en écrivant une bibliothèque procédurale, sans aucun objet.
David Richerby

2
@DavidRicherby, nous avions la bibliothèque procédurale, mais c'est ce que nous avons déprécié, il ne s'agissait pas seulement de code partout. Le fait était que nous l'avons fait à l'envers. Personne n'essayait de faire de la POO, c'est arrivé naturellement.
paIncrease

@DavidRicherby pouvez-vous donner des exemples d'implémentations de bibliothèques procédurales afin que je puisse m'assurer que nous parlons de la même chose?
paIncrease

2
Merci pour votre réponse et +1. Il y a longtemps, un autre programmeur expérimenté a partagé comment OOP a rendu son projet plus fiable forums.devshed.com/programming-42/… Je suppose que OOP a été conçu de manière très intelligente par certains professionnels qui ont pu rencontrer des problèmes d'approche procédurale.
user31782

2

il existe de nombreuses affirmations et intentions sur ce que / où la programmation OOP a un avantage sur la programmation procédurale, y compris par ses inventeurs et utilisateurs. mais simplement parce qu'une technologie a été conçue pour un certain but par ses concepteurs ne garantit pas qu'elle réussira dans ces objectifs. il s'agit d'une compréhension clé dans le domaine du génie logiciel datant du célèbre essai de Brooks "No silver bullet" qui est toujours d'actualité malgré la révolution du codage OOP. (voir aussi le cycle de battage Gartner pour les nouvelles technologies.)

beaucoup de ceux qui ont utilisé les deux ont également des opinions d'expérience anecdotiques, et cela a une certaine valeur, mais il est connu dans de nombreuses études scientifiques que l'analyse autodéclarée peut être inexacte. il semble qu'il y ait peu d' analyse quantitative de ces différences ou s'il y en a, elle n'est pas tellement citée. il est un peu étonnant de voir combien informatiques scientifiques parlent sur certains sujets autoritairement centraux dans leur domaine mais ne citent pas réellement la recherche scientifique pour soutenir leurs points de vue et ne se rendent pas compte qu'ils passent réellement sur la sagesse conventionnelle dans leur domaine (quoique très répandue ).

comme il s'agit d'un site / forum scientifique , voici une brève tentative pour mettre les nombreuses opinions sur des bases plus solides et quantifier la différence réelle. il peut y avoir d'autres études et j'espère que d'autres pourraient les signaler s'ils en entendent parler.(Question zen: s'il y a vraiment une différence majeure, et que tant d'efforts massifs dans le domaine du génie logiciel commercial et ailleurs ont été appliqués / investis pour le réaliser, pourquoi les preuves scientifiques de cela seraient-elles si difficiles à trouver? ne devrait-il pas y avoir une référence classique et très citée dans le domaine qui quantifie définitivement la différence?)

cet article utilise une analyse expérimentale / quantitative / scientifique et soutient spécifiquement que la compréhension par les programmeurs débutants est améliorée avec les méthodes de codage OOP dans certains cas mais n'était pas concluante dans d'autres cas (par rapport à la taille des programmes). noter que ceci est seulement l' une des revendications de nombreuses / majeures sur la supériorité POO étant avancé dans d' autres réponses et par les défenseurs de la POO. l'étude mesurait peut-être un élément psychologique connu sous le nom de « compréhension de la charge cognitive / surcharge» par rapport au codage.

  • Une comparaison de la compréhension des programmes orientés objet et procéduraux par les programmeurs novices en interaction avec les ordinateurs. Susan Wiedenbeck, Vennila Ramalingam, Suseela Sarasamma, Cynthia L Corritore (1999)

    Cet article rend compte de deux expériences comparant les représentations mentales et la compréhension des programmes par les novices dans les styles orienté objet et procédural. Les sujets étaient des programmeurs novices inscrits dans un deuxième cours de programmation qui enseignait soit le paradigme orienté objet soit le paradigme procédural. La première expérience a comparé les représentations mentales et la compréhension de programmes courts écrits dans les styles procéduraux et orientés objet. La deuxième expérience a étendu l'étude à un programme plus vaste intégrant des fonctionnalités linguistiques plus avancées. Pour les programmes courts, il n'y avait pas de différence significative entre les deux groupes en ce qui concerne le nombre total de questions ayant répondu correctement, mais les sujets orientés objet étaient supérieurs aux sujets procéduraux pour répondre aux questions sur la fonction du programme. Cela suggère que les informations sur les fonctions étaient plus facilement disponibles dans leurs représentations mentales des programmes et soutiennent un argument selon lequel la notation orientée objet met en évidence la fonction au niveau de la classe individuelle. Pour le programme long, aucun effet correspondant n'a été trouvé. La compréhension des sujets procéduraux était supérieure aux sujets orientés objet sur tous les types de questions. Les difficultés rencontrées par les sujets orientés objet pour répondre aux questions d'un programme plus vaste suggèrent qu'ils ont rencontré des problèmes pour rassembler les informations et en tirer des conclusions. Nous suggérons que ce résultat peut être lié à une courbe d'apprentissage plus longue pour les novices du style orienté objet, ainsi qu'aux caractéristiques du style OO et de la notation particulière du langage OO.

voir également:


1
J'ai du respect pour les études expérimentales. Il y a cependant la question de s'assurer qu'ils répondent aux bonnes questions. Il y a trop de variables dans ce que l'on peut appeler la POO, et dans les façons de l'utiliser, pour qu'une seule étude soit significative, à mon humble avis. Comme beaucoup de choses en programmation, la POO a été créée par des experts pour répondre à leurs propres besoins . En discutant de l'utilité de la POO (que je n'ai pas prise comme sujet du PO, qui est plutôt de savoir si elle aborde une lacune de la programmation procédurale), on peut se demander: quelle fonctionnalité, pour qui, dans quel but? Alors seulement, les études sur le terrain deviennent pleinement significatives.
babou

1
Avertissement d'anecdote: si un problème est assez petit (par exemple jusqu'à environ 500-1000 lignes de code), la POO ne fait aucune différence dans mon expérience, il pourrait même se mettre en travers du fait de devoir se soucier de choses qui font très peu de différence. Si le problème est important et comporte une certaine forme de "pièces interchangeables" qu'il faut en outre pouvoir ajouter ultérieurement (fenêtres dans une interface graphique, appareils dans un système d'exploitation, ...), l' organisation de la discipline OOP est inévitable. Vous pouvez certainement programmer la POO sans prise en charge linguistique (voir par exemple le noyau Linux).
vonbrand

1

Faites attention. Lisez le classique de R. King "Mon chat est orienté objet" dans "Concepts, bases de données et applications orientés objet" (Kim et Lochovsky, éds) (ACM, 1989). "Orienté objet" est devenu plus un mot à la mode qu'un concept clair.

En outre, il existe de nombreuses variantes sur le thème, avec peu en commun. Il existe des langages basés sur des prototypes (l'héritage provient d'objets, il n'y a pas de classes en tant que telles) et des langages basés sur des classes. Il existe des langages qui permettent l'héritage multiple, d'autres qui ne le permettent pas. Certains langages ont une idée comme les interfaces Java (peuvent être considérées comme une forme d'héritage multiple édulcoré). Il y a l'idée des mixins. L'héritage peut être plutôt strict (comme en C ++, ne peut pas vraiment changer ce que vous obtenez dans une sous-classe), ou très généreusement géré (en Perl, la sous-classe peut redéfinir presque tout). Certains langages ont une racine unique pour l'héritage (généralement appelée Object, avec un comportement par défaut), d'autres permettent au programmeur de créer plusieurs arborescences. Certaines langues insistent sur le fait que "tout est un objet", d'autres manipulent des objets et des non-objets, certains (comme Java) ont "la plupart sont des objets, mais ces quelques types ici ne le sont pas". Certains langages insistent sur une encapsulation stricte de l'état dans les objets, d'autres la rendent facultative (C ++ 'privé, protégé, public), d'autres n'ont pas du tout d'encapsulation. Si vous plissez les yeux vers un langage comme Scheme sous l'angle droit, vous voyez la POO intégrée sans effort particulier (peut définir des fonctions qui renvoient des fonctions qui encapsulent un état local).


0

Pour être concis, la programmation orientée objet traite les problèmes de sécurité des données présents dans la programmation procédurale. Cela se fait en utilisant le concept d'encapsulation des données, permettant uniquement aux classes légitimes d'hériter des données. Les modificateurs d'accès facilitent la réalisation de cet objectif. J'espère que cela pourra aider.:)


Quels sont les problèmes de sécurité des données présents dans la programmation procédurale?
user31782

En programmation procédurale, on ne peut pas restreindre l'utilisation d'une variable globale. N'importe quelle fonction pourrait utiliser sa valeur. Cependant, dans la POO, je pouvais restreindre l'utilisation d'une variable à une certaine classe uniquement ou uniquement aux classes qui en héritaient peut-être.
manu

Dans la programmation procédurale, nous pouvons également restreindre l'utilisation de la variable globale en utilisant la variable à certaines fonctions, c'est-à-dire en ne déclarant aucune donnée globale.
user31782

Si vous ne le déclarez pas globalement, ce n'est pas une variable globale.
manu

1
"Sécurisé" ou "Correct" ne signifie rien sans spécification. Ces choses sont des tentatives pour mettre une spécification de code vers cet objectif: Types, Définitions de classe, DesignByContract, etc. Vous obtenez la «sécurité» dans le sens où vous pouvez rendre inviolables les limites des données privées; en supposant que vous devez obéir au jeu d'instructions de la machine virtuelle pour exécuter. L'orientation d'objet ne cachera pas la mémoire interne à quelqu'un qui peut lire la mémoire directement, et une mauvaise conception de protocole d'objet distribue des secrets par conception.
Rob
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.