Prima di OOP, i membri della struttura dei dati erano stati lasciati pubblici?


44

Quando una struttura di dati (ad esempio una coda) viene implementata utilizzando un linguaggio OOP, alcuni membri della struttura di dati devono essere privati ​​(ad esempio, il numero di elementi nella coda).

Una coda può anche essere implementata in un linguaggio procedurale usando ae structun insieme di funzioni che operano su struct. Tuttavia, in un linguaggio procedurale non è possibile rendere i membri di un structprivato. I membri di una struttura di dati implementati in un linguaggio procedurale sono stati lasciati pubblici o c'era qualche trucco per renderli privati?


75
"alcuni membri della struttura dei dati devono essere privati" C'è una grande differenza tra "probabilmente dovrebbe essere" e "deve essere". Dammi una lingua OO e ti garantisco di poter fare una coda che funzioni perfettamente anche con tutti i suoi membri e metodi che sono pubblici, purché tu non abusi di tutta quella libertà.
8bittree,

48
Per dare una svolta diversa a ciò che ha detto @ 8bittree, avere tutto in pubblico va bene se le persone che usano il tuo codice sono abbastanza disciplinate da attenersi all'interfaccia che hai impostato. Il costrutto del membro privato è nato a causa di persone che non potevano tenere il naso fuori da dove non appartenevano.
Blrfl,

20
Intendevi "prima che l'incapsulamento diventasse popolare"? L'incapsulamento era piuttosto popolare prima che le lingue OO diventassero popolari.
Frank Hileman,

6
@FrankHileman Penso che questo sia in realtà il nocciolo della domanda: OP vuole sapere se esistesse l'incapsulamento nei linguaggi procedurali, prima di Simula / Smalltalk / C ++
dcorking

18
Mi dispiace in anticipo se questo sembra condiscendente, non intendo che lo sia. Devi imparare altre lingue. I linguaggi di programmazione non sono per le macchine da eseguire, sono per i programmatori a cui pensare . Danno necessariamente forma al tuo modo di pensare. Semplicemente non avresti questa domanda se avessi trascorso del tempo significativo lavorando con JavaScript / Python / Ocaml / Clojure, anche se avessi fatto Java tutto il giorno nel tuo lavoro quotidiano. A parte un progetto open source in C ++ su cui lavoro (che è per lo più in C), non ho mai usato un linguaggio con modificatori di accesso dal college e non mi sono perso.
Jared Smith,

Risposte:


139

OOP non ha inventato l'incapsulamento e non è sinonimo di incapsulamento. Molti linguaggi OOP non hanno modificatori di accesso in stile C ++ / Java. Molti linguaggi non OOP hanno varie tecniche disponibili per offrire l'incapsulamento.

Un approccio classico per l'incapsulamento sono le chiusure , utilizzate nella programmazione funzionale . Questo è significativamente più vecchio di OOP ma è in qualche modo equivalente. Ad esempio in JavaScript potremmo creare un oggetto come questo:

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

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

L' plus2oggetto sopra non ha alcun membro che consentirebbe l'accesso diretto a x- è interamente incapsulato. Il add()metodo è una chiusura sulla xvariabile.

Il linguaggio C supporta alcuni tipi di incapsulamento attraverso il meccanismo del file di intestazione , in particolare la tecnica del puntatore opaco . In C, è possibile dichiarare un nome di struttura senza definirne i membri. A quel punto nessuna variabile del tipo di quella struttura può essere usata, ma possiamo usare i puntatori a quella struttura liberamente (perché la dimensione di un puntatore alla struttura è nota al momento della compilazione). Ad esempio, considera questo file di intestazione:

#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

Ora possiamo scrivere codice che utilizza questa interfaccia di Adder, senza avere accesso ai suoi campi, ad esempio:

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

E qui ci sarebbero i dettagli dell'implementazione totalmente incapsulati:

#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;
}

Esiste anche la classe di linguaggi di programmazione modulari , che si concentra su interfacce a livello di modulo. La famiglia linguistica ML incl. OCaml include un approccio interessante ai moduli chiamati funzioni . OOP ha messo in ombra la programmazione modulare ampiamente utilizzata, ma molti presunti vantaggi di OOP riguardano più la modularità che l'orientamento agli oggetti.

C'è anche l'osservazione che le classi in linguaggi OOP come C ++ o Java spesso non vengono utilizzate per oggetti (nel senso di entità che risolvono operazioni mediante associazione tardiva / invio dinamico) ma semplicemente per tipi di dati astratti (dove definiamo un'interfaccia pubblica che si nasconde dettagli di implementazione interna). Il documento On Understanding Data Abstraction, Revisited (Cook, 2009) discute questa differenza in modo più dettagliato.

Sì, molte lingue non hanno alcun meccanismo di incapsulamento. In queste lingue, i membri della struttura vengono lasciati pubblici. Al massimo, ci sarebbe una convenzione di denominazione che scoraggia l'uso. Ad esempio, penso che Pascal non avesse un meccanismo di incapsulamento utile.


11
Vedi l'errore in Adder self = malloc(sizeof(Adder));? C'è un motivo per cui si sizeof(TYPE)stanno digitando i puntatori ed è generalmente disapprovato.
Deduplicatore,

10
Non puoi semplicemente scrivere sizeof(*Adder), perché *Addernon è un tipo, così come *int *non è un tipo. L'espressione T t = malloc(sizeof *t)è sia idiomatica che corretta. Vedi la mia modifica.
wchargin,

4
Pascal aveva variabili unitarie che non potevano essere viste dall'esterno di quell'unità. In effetti le variabili unitarie erano equivalenti alle private staticvariabili in Java. Analogamente a C, è possibile utilizzare i puntatori opachi per passare i dati in Pascal senza dichiarare di cosa si trattasse. MacOS classico utilizzava molti puntatori opachi poiché le parti pubbliche e private di un record (struttura dati) potevano essere passate insieme. Ricordo che Window Manager stava facendo molto di tutto ciò poiché parti del Window Record erano pubbliche ma anche alcune informazioni interne erano incluse.
Michael Shopsin

6
Forse un esempio migliore di Pascal è Python, che supporta l'orientamento agli oggetti ma non l'incapsulamento, ricorrendo a convenzioni di denominazione come _private_membere output_property_, o tecniche più avanzate per la creazione di oggetti immaginabili.
Mephy,

11
C'è una fastidiosa tendenza nella letteratura OOD a presentare ogni principio di progettazione come principio di progettazione OO . La letteratura (non accademica) OOD tende a dipingere un quadro di "secoli bui" in cui tutti facevano tutto di sbagliato, e poi i praticanti OOP portano la luce. Per quanto ne so, questo deriva principalmente dall'ignoranza. Ad esempio, per quanto ne so, Bob Martin ha dato uno sguardo serio alla programmazione funzionale solo pochi anni fa.
Derek Elkins,

31

Innanzitutto, l'essere procedurali rispetto agli oggetti non ha nulla a che fare con pubblico vs privato. Molti linguaggi orientati agli oggetti non hanno nozione di controllo degli accessi.

In secondo luogo, in "C" - che la maggior parte delle persone chiamerebbe procedurale, e non orientata agli oggetti, ci sono molti trucchi che puoi usare per rendere effettivamente le cose private. Uno molto comune è usare puntatori opachi (es. Void *). Oppure: è possibile inoltrare un oggetto e non definirlo in un file di intestazione.

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) { ... }

Guarda Windows SDK! Utilizza HANDLE e UINT_PTR, e cose del genere come handle generici della memoria utilizzata nelle API - rendendo effettivamente le implementazioni private.


1
Il mio campione ha dimostrato un approccio migliore (C), usando le strutture dichiarate in avanti. per usare l'approccio void * userei typedefs: nel file .h dire typedef void * queue, e quindi ovunque avevamo la coda struct basta dire coda; Quindi, nel file .c, rinominare la coda struct in struct queueImpl e tutti gli argomenti diventano coda (non si trova nella coda struct *) e la prima riga di codice per ciascuna di tali funzioni diventa struct queueImpl * qi = (struct queueImpl *) q
Lewis Pringle,

7
Hmm. Lo rende privato perché non è possibile accedere (leggere o scrivere) a nessun campo della 'coda' da nessun'altra parte oltre alla sua implementazione (file foo.c). Cos'altro intendevi per privato? A proposito: questo è vero per ENTRAMBE l'approccio vuoto void * e l'approccio (migliore) per dichiarare in avanti
Lewis Pringle,

5
Devo confessare che sono passati quasi 40 anni da quando ho letto il libro su smalltalk-80, ma non ricordo alcuna nozione di membri di dati pubblici o privati. Penso che anche CLOS non avesse questa idea. L'oggetto Pascal non aveva questa idea. Ricordo che Simula lo fece (probabilmente dove Stroustrup ebbe l'idea), e la maggior parte dei linguaggi OO dal C ++ ce l'hanno. Ad ogni modo, concordiamo che l'incapsulamento e i dati privati ​​sono buone idee. Anche l'interrogatore originale era chiaro su questo punto. Stava solo chiedendo: come facevano i vecchi a incapsulare nei linguaggi pre-C ++.
Lewis Pringle,

5
@LewisPringle non c'è menzione dei membri di dati pubblici in Smalltalk-80 perché tutte le "variabili di istanza" (membri di dati) sono private, a meno che non si usi la riflessione. AFAIU Smalltalkers scrive un programma di accesso per ogni variabile che desidera rendere pubblica.
Dal

4
@LewisPringle, al contrario, tutti i "metodi" (membri della funzione) di Smalltalk sono pubblici (ci sono convenzioni goffe per contrassegnarli come privati)
dcorking

13

"Tipi di dati opachi" era un concetto ben noto quando ho conseguito la laurea in informatica 30 anni fa. Non coprivamo OOP poiché all'epoca non era di uso comune e la "programmazione funzionale" era considerata più corretta.

Modula-2 aveva il supporto diretto per loro, vedi https://www.modula2.org/reference/modules.php .

Lewis Pringle ha già spiegato in che modo è possibile utilizzare in avanti la dichiarazione di una struttura in C. A differenza del Modulo 2, è stata fornita una funzione di fabbrica per creare l'oggetto. ( I metodi virtuali erano anche facili da implementare in C avendo il primo membro di una struttura come puntatore a un'altra struttura che conteneva puntatori di funzione ai metodi.)

Spesso veniva anche usata la convenzione. Ad esempio, non è possibile accedere a nessun campo che inizia con "_" al di fuori del file che possiede i dati. Ciò è stato facilmente applicato dalla creazione di strumenti di controllo personalizzati.

Ogni progetto su larga scala su cui ho lavorato (prima di passare a C ++ e poi C #) aveva un sistema in atto per impedire l'accesso ai dati "privati" con il codice sbagliato. Era solo un po 'meno standardizzato di adesso.


9

Nota che esistono molte lingue OO senza la possibilità integrata di contrassegnare i membri come privati. Questo può essere fatto per convenzione, senza che il compilatore debba applicare la privacy. Ad esempio, le persone spesso aggiungono il prefisso alle variabili private con un carattere di sottolineatura.

Esistono tecniche per rendere più difficile l'accesso alle variabili "private", la più comune delle quali è il linguaggio PIMPL . Questo mette le tue variabili private in una struttura separata, con solo un puntatore allocato nei tuoi file di intestazione pubblici. Ciò significa una maggiore dereferenza e un cast per ottenere qualsiasi variabile privata, qualcosa del genere ((private_impl)(obj->private))->actual_value, che diventa fastidioso, quindi in pratica viene usato raramente.


4

Le strutture dati non avevano "membri", solo campi di dati (supponendo che fosse un tipo di record). La visibilità era in genere impostata per l'intero tipo. Tuttavia, ciò potrebbe non essere così limitato come pensi, perché le funzioni non facevano parte del record.

Torniamo indietro e prendiamo un po 'di storia qui ...

Il paradigma di programmazione dominante prima di OOP era chiamato programmazione strutturata . L'obiettivo principale iniziale di questo era di evitare l'uso di istruzioni di salto non strutturate ("goto" s). Questo è un paradigma orientato al flusso di controllo (mentre OOP è più orientato ai dati), ma era comunque un'estensione naturale per tentare di mantenere i dati logicamente strutturati proprio come il codice.

Un altro risultato della programmazione strutturata è stato il nascondere le informazioni , l'idea che le implementazioni della struttura del codice (che probabilmente cambieranno abbastanza spesso) dovrebbero essere tenute separate dall'interfaccia (che idealmente non cambierebbe quasi altrettanto). Ora è dogma, ma ai vecchi tempi molte persone consideravano davvero migliore per ogni sviluppatore conoscere i dettagli dell'intero sistema, quindi questa volta era in realtà un'idea controversa. L'edizione originale di The Mythical Man Month di Brook ha effettivamente discusso contro il nascondiglio delle informazioni.

I linguaggi di programmazione successivi progettati esplicitamente per essere buoni linguaggi di programmazione strutturata (ad esempio, Modula-2 e Ada) generalmente includevano il nascondere le informazioni come un concetto fondamentale, costruito attorno a un qualche tipo di concetto di una funzione coesiva di funzioni (e qualsiasi tipo, costante e oggetti che potrebbero richiedere). In Modula-2 questi erano chiamati "Moduli", in "Pacchetti" di Ada. Molti linguaggi OOP moderni chiamano lo stesso concetto "spazi dei nomi". Questi spazi dei nomi erano il fondamento organizzativo dello sviluppo in questi linguaggi e per la maggior parte degli scopi potevano essere usati in modo simile alle classi OOP (senza supporto reale per l'eredità, ovviamente).

Quindi, in Modula-2 e Ada (83) si poteva dichiarare qualsiasi routine, tipo, costante, o un oggetto in uno spazio dei nomi privato o pubblico, ma se si ha un tipo di record, non c'era (facile) modo per dichiarare alcuni record di campi pubblici e altri privati. O tutto il tuo disco è pubblico o non lo è.


Ho trascorso un bel po 'di tempo a lavorare in Ada. Il nascondersi selettivo (di una parte di un tipo di dati) era qualcosa che facevamo continuamente; nel pacchetto contenente, definiresti il ​​tipo stesso come privato o limitato; l'interfaccia del pacchetto esporrebbe funzioni / procedure pubbliche per ottenere e / o impostare campi interni. Queste routine dovrebbero ovviamente prendere un parametro di tipo privato. Allora non l'ho fatto e ora non lo considero difficile.
David,

Inoltre, la maggior parte delle lingue OFA di AFAIK funzionano allo stesso modo, ovvero myWidget.getFoo () è implementato come getFoo (myWidget). L' object.method()invocazione è solo zucchero sintattico. IMHO importante - vedi Principio di Meyer di accesso / riferimento uniforme - ma ancora solo zucchero sintattico.
David,

@ David - Questo è stato l'argomento della comunità Ada per anni durante l'era Ada 95. Credo che alla fine abbiano ceduto e dimostrato la propria argomentazione consentendo object.method()come forma alternativa method(object, ...) per le persone che non sono riuscite a fare il salto concettuale.
TED

0

In C potresti già passare i puntatori a tipi dichiarati ma non definiti, come altri hanno già detto, limitando in effetti l'accesso a tutti i campi.

Puoi anche avere funzioni pubbliche e private da un modulo all'altro. Le funzioni dichiarate statiche nel file di origine non sono visibili all'esterno, anche se si tenta di indovinarne il nome. Allo stesso modo, puoi avere variabili globali statiche a livello di file, il che è generalmente una cattiva pratica ma consente l'isolamento su base modulo.

È probabilmente importante sottolineare che la restrizione dell'accesso come una convenzione ben standardizzata piuttosto che un costrutto imposto dal linguaggio funziona bene (vedi Python). Inoltre, la limitazione dell'accesso ai campi oggetto protegge il programmatore solo quando è necessario modificare il valore dei dati all'interno di un oggetto dopo la creazione. Che è già un odore di codice. Probabilmente, la constparola chiave di C e in particolare la parola chiave di C ++ per metodi e argomenti di funzione è di gran lunga più utile per il programmatore rispetto a quella piuttosto scarsa di Java final.


L'unica caratteristica che C aveva specificamente per nascondere le informazioni erano i staticdati e le operazioni globali (il che significava che non venivano presentati al linker per l'uso da altre compilazioni). Si può plausibilmente sostenere che qualsiasi supporto C abbia avuto per le buone pratiche di progettazione del software a parte che era praticamente un trucco, e non faceva parte del design originale della lingua nel 1972.
TED

0

Se la tua definizione di pubblico è la capacità di accedere all'implementazione e ai dati / proprietà tramite il tuo codice in qualsiasi momento, la risposta è semplicemente: . Tuttavia, è stato astratto con diversi mezzi, a seconda della lingua.

Spero che questo abbia risposto brevemente alla tua domanda.


-1

Ecco un contro-esempio molto semplice: in Java, interfacedefiniamo gli oggetti, ma classnon lo fanno. A classdefinisce un tipo di dati astratto, non un oggetto.

Ergo, ogni volta che lo usi privatein classJava, hai un esempio di una struttura di dati con membri privati ​​che non è orientata agli oggetti.


7
Questa risposta è ovviamente tecnicamente corretta, ma è totalmente incomprensibile per chiunque non sappia già cosa sono gli ADT e come sono diversi dagli oggetti.
amon,

1
Ho imparato qualcosa da questa risposta.
littleO

3
Le interfacce non "definiscono" gli oggetti; essi specificano i contratti per operazioni / comportamenti che gli oggetti possono fare o eseguire. Proprio come l'ereditarietà è generalmente descritta da un è un rapporto e la composizione da un ha un rapporto, le interfacce sono generalmente descritti da possono fare le relazioni.
code_dredd,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.