Avant la POO, les membres de la structure de données étaient-ils restés publics?

44

Lorsqu'une structure de données (par exemple, une file d'attente) est implémentée à l'aide d'un langage POO, certains membres de la structure de données doivent être privés (par exemple, le nombre d'éléments de la file d'attente).

Une file d'attente peut également être implémentée dans un langage procédural à l'aide d'un structet d'un ensemble de fonctions qui fonctionnent sur le serveur struct. Cependant, dans un langage procédural, vous ne pouvez pas faire des membres d'un structprivé. Les membres d'une structure de données implémentée dans un langage procédural ont-ils été rendus publics ou y avait-il un truc pour les rendre privés?

Christopher
la source
75
"Certains membres de la structure de données doivent être privés" Il y a une grande différence entre "devrait probablement être" et "doit être". Donnez-moi un langage OO et je vous garantis que je peux faire une file d’attente qui fonctionne parfaitement, même si tous ses membres et méthodes sont publics, à condition de ne pas abuser de toute cette liberté.
8bittree
48
Pour donner une tournure différente à ce que @ 8bittree a dit, il est correct que tout soit public si les utilisateurs de votre code sont suffisamment disciplinés pour respecter l'interface que vous avez définie. La structure des membres privés a été créée à cause de personnes qui ne pouvaient pas garder le nez loin de l'endroit où elles n'appartenaient pas.
Blrfl
20
Voulez-vous dire "avant que l'encapsulation soit devenue populaire"? L'encapsulation était assez populaire avant que les langues OO ne le deviennent.
Frank Hileman
6
@FrankHileman Je pense que c'est en fait le cœur de la question: OP veut savoir si l'encapsulation existait dans les langages procéduraux, avant Simula / Smalltalk / C ++
débutant le
18
Je suis désolé d'avance si cela semble condescendant, je ne le veux pas. Vous devez apprendre d'autres langues. Les langages de programmation ne sont pas conçus pour être utilisés par des machines, mais par les programmeurs . Ils façonnent nécessairement votre façon de penser. Vous n'auriez tout simplement pas cette question si vous aviez passé beaucoup de temps à travailler avec JavaScript / Python / Ocaml / Clojure, même si vous utilisiez Java toute la journée à votre travail. En dehors d’un projet open source C ++ sur lequel je travaille (c’est la plupart du temps C), je n’ai pas vraiment utilisé de langage avec des modificateurs d’accès depuis le collège, et je ne les ai pas manqués.
Jared Smith

Réponses:

139

OOP n'a pas inventé l'encapsulation et n'est pas synonyme d'encapsulation. De nombreux langages POO n'ont pas de modificateurs d'accès de style C ++ / Java. De nombreuses langues non-POO disposent de diverses techniques pour offrir une encapsulation.

Une approche classique de l’encapsulation est la fermeture , utilisée dans la programmation fonctionnelle . C'est beaucoup plus vieux que la POO mais est en quelque sorte équivalent. Par exemple, en JavaScript, nous pourrions créer un objet comme ceci:

function Adder(x) {
  this.add = function add(y) {
    return x + y;
  }
}

var plus2 = new Adder(2);
plus2.add(7);  //=> 9

L' plus2objet ci-dessus n'a aucun membre qui permettrait un accès direct à x- il est entièrement encapsulé. La add()méthode est une fermeture sur la xvariable.

Le langage C prend en charge certains types d'encapsulation via son mécanisme de fichier d'en-tête , notamment la technique du pointeur opaque . En C, il est possible de déclarer un nom de structure sans définir ses membres. À ce stade, aucune variable du type de cette structure ne peut être utilisée, mais nous pouvons utiliser librement les pointeurs sur cette structure (car la taille d'un pointeur de structure est connue au moment de la compilation). Par exemple, considérons ce fichier d’en-tête:

#ifndef ADDER_H
#define ADDER_H

typedef struct AdderImpl *Adder;

Adder Adder_new(int x);
void Adder_free(Adder self);
int Adder_add(Adder self, int y);

#endif

Nous pouvons maintenant écrire du code qui utilise cette interface Adder, sans avoir accès à ses champs, par exemple:

Adder plus2 = Adder_new(2);
if (!plus2) abort();
printf("%d\n", Adder_add(plus2, 7));  /* => 9 */
Adder_free(plus2);

Et voici les détails de la mise en œuvre totalement encapsulée:

#include "adder.h"

struct AdderImpl { int x; };

Adder Adder_new(int x) {
  Adder self = malloc(sizeof *self);
  if (!self) return NULL;
  self->x = x;
  return self;
}

void Adder_free(Adder self) {
  free(self);
}

int Adder_add(Adder self, int y) {
  return self->x + y;
}

Il existe également la classe des langages de programmation modulaires , qui se concentre sur les interfaces au niveau des modules. La famille de langues ML incl. OCaml inclut une approche intéressante des modules appelés foncteurs . La programmation modulaire occultée et en grande partie occultée par la programmation orientée objet, mais de nombreux avantages supposés de la programmation orientée objet concernent davantage la modularité que l'orientation objet.

Il y a aussi l'observation que les classes dans les langages POO tels que C ++ ou Java ne sont souvent pas utilisées pour les objets (dans le sens d'entités qui résolvent des opérations par liaison / dispatch dynamique tardive) mais simplement pour les types de données abstraits (où nous définissons une interface publique qui cache détails de la mise en œuvre interne). Le document intitulé Comprendre l'abstraction des données, revisité (Cook, 2009) examine cette différence plus en détail.

Mais oui, beaucoup de langues n’ont aucun mécanisme d’encapsulation. Dans ces langues, les membres de la structure sont laissés publics. Tout au plus, il y aurait une convention de nommage décourageant l'utilisation. Par exemple, je pense que Pascal n'avait pas de mécanisme d'encapsulation utile.

Amon
la source
11
Voir l'erreur dans Adder self = malloc(sizeof(Adder));? Il y a une raison pour laquelle les pointeurs sont courants et sizeof(TYPE)sont généralement mal vus.
Deduplicator
10
Vous ne pouvez pas simplement écrire sizeof(*Adder), car ce *Addern'est pas un type, tout comme ce *int *n'est pas un type. L'expression T t = malloc(sizeof *t)est à la fois idiomatique et correcte. Voir mon édition.
wchargin
4
Pascal avait des variables d'unité qui ne pouvaient pas être vues de l'extérieur de cette unité. Effectivement, les variables unitaires étaient équivalentes aux private staticvariables en Java. De même en C, vous pouvez utiliser des pointeurs opaques pour transmettre des données en Pascal sans déclarer ce dont il s'agissait. MacOS classique utilisait beaucoup de pointeurs opaques, car des parties publiques et privées d’un enregistrement (structure de données) pouvaient être transmises ensemble. Je me souviens que Windows Manager en faisait beaucoup puisque certaines parties du Window Record étaient publiques mais que certaines informations internes étaient également incluses.
Michael Shopsd
6
Peut-être un meilleur exemple que Pascal est-il Python, qui prend en charge l'orientation objet mais pas l'encapsulation, en recourant à des conventions de dénomination telles que _private_memberet output_property_, ou à des techniques plus avancées de création d'objets imputables.
Mephy
11
La littérature OOD a une fâcheuse tendance à présenter chaque principe de conception comme un principe de conception OO . La littérature OOD (non académique) a tendance à brosser un tableau d'un "âge sombre" où tout le monde faisait tout faux, et ensuite les praticiens de la POO apportaient la lumière. Autant que je sache, cela provient principalement de l'ignorance. Par exemple, Bob Martin a jeté un regard sérieux sur la programmation fonctionnelle il y a quelques années à peine.
Derek Elkins
31

Premièrement, être procédural ou orienté objet n'a rien à voir avec public ou privé. Beaucoup de langages orientés objet n'ont aucune notion de contrôle d'accès.

Deuxièmement, dans "C" - que la plupart des gens appellent procédure, et non orientée objet, vous pouvez utiliser de nombreuses astuces pour rendre les choses privées. Un très commun consiste à utiliser des pointeurs opaques (par exemple, void *). Ou - vous pouvez transférer la déclaration d'un objet sans la définir dans un fichier d'en-tête.

foo.h:

struct queue;
struct queue* makeQueue();
void add2Queue(struct queue* q, int value);
...

foo.c:

struct queue {
    int* head;
    int* head;
};
struct queue* makeQueue() { .... }
void add2Queue(struct queue* q, int value) { ... }

Regardez le SDK de Windows! Il utilise HANDLE et UINT_PTR, entre autres choses, pour constituer des descripteurs génériques de la mémoire utilisée dans les API, ce qui permet de rendre les implémentations privées.

Lewis Pringle
la source
1
Mon échantillon a démontré une meilleure approche (C) - en utilisant des structures déclarées en avant. Pour utiliser l'approche void *, j'utiliserais typedefs: Dans le fichier .h, dites file d'attente typedef void *, puis partout nous avions une file d'attente structurée, dites simplement file d'attente; Ensuite , dans le fichier .c, renommage struct file d' attente pour struct queueImpl, et tous les arguments file d' attente deviennent (isntead de struct file *) et la première ligne de code pour chaque fonction devient struct queueImpl * qi = (struct queueImpl *) q
Lewis Pringle
7
Hmm. Cela le rend privé car vous ne pouvez accéder (lire ou écrire) à aucun champ de la "file d'attente" à partir de n'importe quel endroit en dehors de son implémentation (fichier foo.c). Que vouliez-vous dire par privé? BTW - c'est vrai pour les deux le typedef void * apporach et l'approche (meilleur) avant déclarer struct
Lewis Pringle
5
Je dois avouer que cela fait presque 40 ans que j'ai lu le livre sur smalltalk-80, mais je ne me souviens pas de la notion de données publiques ou privées. Je pense que CLOS n'avait pas non plus cette notion. Object Pascal n'avait pas cette notion. Je me souviens de ce que Simula a fait (probablement où Stroustrup a eu l’idée), et la plupart des langages OO depuis C ++ l’ont. Quoi qu'il en soit, nous convenons que l'encapsulation et les données privées sont de bonnes idées. Même le questionneur initial était clair sur ce point. Il demandait simplement: comment les anciens assuraient-ils l'encapsulation dans des langages pré-C ++?
Lewis Pringle
5
@LewisPringle, il n'est pas fait mention de membres de données publics dans Smalltalk-80 car toutes les "variables d'instance" (membres de données) sont privées, sauf si vous utilisez la réflexion. AFAIU Smalltalkers écrit un accesseur pour chaque variable à rendre publique.
départ le
4
@LewisPringle en revanche, toutes les "méthodes" Smalltalk (membres de fonctions) sont publiques (il existe des conventions maladroites pour les marquer comme privées)
dcorking
13

"Les types de données opaques" était un concept bien connu lorsque j'ai obtenu mon diplôme en informatique il y a 30 ans. Nous n'avons pas couvert la POO car ce n'était pas d'usage courant à l'époque et la "programmation fonctionnelle" était considérée comme plus correcte.

Modula-2 avait un support direct pour eux, voir https://www.modula2.org/reference/modules.php .

Lewis Pringle a déjà expliqué comment la déclaration anticipée d'une structure peut être utilisée en C. Contrairement à Module-2, une fonction de fabrique devait être fournie pour créer l'objet. ( Les méthodes virtuelles étaient également faciles à implémenter en C, le premier membre d'une structure étant un pointeur sur une autre structure contenant des pointeurs de fonction sur les méthodes.)

Souvent, la convention était également utilisée. Par exemple, aucun champ commençant par «_» ne doit être accessible en dehors du fichier contenant les données. Cela a été facilement appliqué par la création d’outils de vérification personnalisés.

Tous les projets à grande échelle sur lesquels j'ai travaillé (avant de passer à C ++ puis à C #) disposaient d'un système en place pour empêcher l'accès aux données "privées" par un code erroné. C'était juste un peu moins standardisé que maintenant.

Ian
la source
9

Notez qu'il existe de nombreuses langues OO sans possibilité intégrée de marquer les membres comme privés. Cela peut être fait par convention, sans qu'il soit nécessaire que le compilateur applique la confidentialité. Par exemple, les personnes préfixent souvent les variables privées par un trait de soulignement.

Il existe des techniques pour rendre plus difficile l'accès aux variables "privées", la plus courante étant l' idiome PIMPL . Cela place vos variables privées dans une structure séparée, avec juste un pointeur alloué dans vos fichiers d’en-têtes publics. Cela signifie une déréférence supplémentaire et une conversion pour obtenir toutes les variables privées, quelque chose comme ((private_impl)(obj->private))->actual_value, ce qui devient gênant, donc, en pratique, il est rarement utilisé.

Karl Bielefeldt
la source
4

Les structures de données n'avaient pas de "membres", seulement des champs de données (en supposant qu'il s'agisse d'un type d'enregistrement). La visibilité était généralement définie pour tout le type. Cependant, cela n’est peut-être pas aussi contraignant que vous le pensez, car les fonctions ne faisaient pas partie du dossier.

Revenons un peu en arrière et obtenons un peu d'histoire ici ...

Le paradigme de programmation dominant avant la POO s'appelait la programmation structurée . L’objectif principal initial était d’éviter l’utilisation d’énoncés de saut non structurés ("goto"). Il s’agit d’un paradigme axé sur les flux de contrôle (alors que la programmation orientée objet est davantage axée sur les données), mais il s’agissait toujours d’une extension naturelle de ce dernier pour tenter de garder les données structurées logiquement, tout comme le code.

Une autre conséquence de la programmation structurée a été la dissimulation d'informations , l'idée selon laquelle les implémentations de la structure du code (qui est susceptible de changer assez souvent) devraient être séparées de l'interface (idéalement, elles ne devraient pas changer autant). C'est maintenant un dogme, mais à l'époque jadis, beaucoup de gens considéraient qu'il était préférable que chaque développeur connaisse les détails du système dans son ensemble. Il s'agissait donc à un moment d'une idée controversée. L'édition originale de The Mythical Man Month, de Brook, plaidait en réalité contre la dissimulation d'informations.

Les langages de programmation ultérieurs conçus explicitement pour être bons Les langages de programmation structurés (par exemple, Modula-2 et Ada) incluaient généralement le masquage d’informations en tant que concept fondamental, construit autour d’une sorte de concept de fonctionnalité cohérente de fonctions (et de tous types, constantes et objets dont ils pourraient avoir besoin). Dans Modula-2, ils ont été appelés "Modules", dans Ada "Packages". Beaucoup de langages POO modernes appellent le même concept "espaces de noms". Ces espaces de noms constituaient le fondement organisationnel du développement de ces langages et pouvaient, dans la plupart des cas, être utilisés de la même manière que les classes POO (sans prise en charge réelle de l'héritage, bien entendu).

Ainsi, dans Modula-2 et Ada (83), vous pouvez déclarer une routine, un type, une constante ou un objet dans un espace de noms privé ou public, mais si vous aviez un type d'enregistrement, il n'existait aucun moyen (facile) de déclarer certains champs d' enregistrement public. et d'autres privés. Tout votre dossier est public ou non.

TED
la source
J'ai passé pas mal de temps à travailler à Ada. La dissimulation sélective (d'une partie d'un type de données) était quelque chose que nous faisions tout le temps; dans le paquet conteneur, vous définiriez le type lui-même comme privé ou privé limité; l'interface de package exposerait les fonctions / procédures publiques pour obtenir et / ou définir des champs internes. Ces routines devront bien sûr prendre un paramètre de type privé. Je n'ai pas alors et ne considère pas maintenant cela difficile.
David
De plus, autant que je sache, la plupart des langages OO fonctionnent de la même façon, c’est-à-dire que myWidget.getFoo () est vraiment implémenté comme getFoo (myWidget). L' object.method()invocation est simplement du sucre syntaxique. Important IMHO - voir le principe d’uniformité d’accès / de référence de Meyer - mais toujours du sucre syntaxique.
David
@ David - C'était l'argument de la communauté Ada pendant des années au cours de l'ère Ada 95. Je crois qu'ils ont finalement cédé et prouvé leur propre argument en offrant object.method()une forme alternative method(object, ...) aux personnes qui ne pouvaient tout simplement pas faire le saut conceptuel.
TED
0

En C, vous pouvez déjà faire passer des pointeurs vers des types déclarés mais non définis, comme d'autres l'ont dit, limitant ainsi l'accès à tous les champs.

Vous pouvez également avoir des fonctions privées et publiques module par module. Les fonctions déclarées statiques dans le fichier source ne sont pas visibles de l'extérieur, même si vous essayez de deviner leur nom. De même, vous pouvez avoir des variables globales statiques au niveau du fichier, ce qui est généralement une mauvaise pratique, mais permet une isolation par module.

Il est probablement important de souligner que la restriction d'accès en tant que convention bien normalisée plutôt que comme construction imposée par la langue fonctionne parfaitement (voir Python). De plus, la restriction de l'accès aux champs d'objet ne protégera le programmeur que lorsqu'il sera nécessaire de modifier la valeur des données dans un objet après sa création. Ce qui est déjà une odeur de code. On peut soutenir que le constmot clé de C et en particulier C ++ pour les méthodes et les arguments de fonction est une aide beaucoup plus utile pour le programmeur que pour celui de Java, qui est plutôt médiocre final.

Kafein
la source
La seule caractéristique C qui était spécifiquement destinée à la dissimulation d'informations était staticles données et opérations globales (ce qui signifiait qu'elles n'étaient pas présentées à l'éditeur de liens pour être utilisées à partir d'autres compilations). Vous pouvez raisonnablement affirmer que tout soutien de C avait de bonnes pratiques en matière de conception de logiciels, mis à part qu'il s'agissait en quelque sorte d'un bidouillage, et qu'il ne faisait pas partie de la conception originale du langage en 1972.
TED le
0

Si votre définition de Public est la possibilité d'accéder à la mise en œuvre et aux données / propriétés via votre propre code à tout moment, la réponse est simplement: Oui . Cependant, il a été résumé par divers moyens - en fonction de la langue.

J'espère que ceci a répondu succinctement à votre question.

RobMac
la source
-1

Voici un contre-exemple très simple: en Java, interfaces définit des objets, mais classce n'est pas le cas. A classdéfinit un type de données abstrait, pas un objet.

Ainsi, chaque fois que vous utilisez privateune version classJava, vous avez un exemple de structure de données avec des membres privés qui n'est pas orientée objet.

Jörg W Mittag
la source
7
Cette réponse est certes techniquement correcte, mais elle est totalement incompréhensible pour quiconque ne sait pas déjà en quoi consiste ADT et en quoi elles sont différentes des objets.
amon
1
J'ai appris quelque chose de cette réponse.
littleO
3
Interfaces ne pas les objets « définir »; ils spécifient des contrats pour les opérations / comportements que les objets peuvent effectuer ou exécuter. Tout comme l' héritage est généralement décrit par une est une relation et de la composition par un a une relation, les interfaces sont généralement décrites par peut faire des relations.
code_dredd