Qual è la differenza tra tipi di dati astratti e oggetti?


11

Una risposta su Programmers.SE caratterizza un saggio di Cook ( Gli oggetti non sono ADT ) come detto

  • Gli oggetti si comportano come una funzione caratteristica rispetto ai valori di un tipo, piuttosto che come algebra. Gli oggetti usano l'astrazione procedurale piuttosto che l'astrazione del tipo

  • Gli ADT di solito hanno un'implementazione unica in un programma. Quando la propria lingua ha moduli, è possibile avere più implementazioni di un ADT, ma di solito non possono interagire.

Mi sembra che, nel saggio di Cook, capita solo che, per l'esempio specifico di un set usato nella carta di Cook, un oggetto possa essere visto come una funzione caratteristica . Non penso che gli oggetti, in generale, possano essere visti come funzioni caratteristiche.

Inoltre, l'articolo di Aldritch Il potere dell'interoperabilità: perché gli oggetti sono inevitabili ¹ suggerisce

La definizione di Cook identifica essenzialmente l'invio dinamico come la caratteristica più importante dell'oggetto

d'accordo con questo e con Alan Kay quando ha detto

OOP per me significa solo messaggistica, conservazione locale, protezione e occultamento del processo statale ed estremo vincolo tardivo di tutte le cose.

Tuttavia, queste diapositive della lezione complementare al documento di Aldritch suggeriscono che le classi Java sono ADT mentre le interfacce Java sono oggetti - e infatti l'uso di interfacce "oggetti" può interagire (una delle caratteristiche chiave di OOP come dato da uno dei punti elenco sopra ).

Le mie domande sono

  1. Ho ragione a dire che le funzioni caratteristiche non sono una caratteristica chiave degli oggetti e che Frank Shearar si sbaglia?

  2. I dati che dialogano tra loro tramite le interfacce Java sono esempi di oggetti anche se non utilizzano l'invio dinamico? Perché? (La mia comprensione è che l'invio dinamico è più flessibile e che le interfacce sono un passo verso la messaggistica in stile obiettivo-C / smalltalk / erlang.)

  3. L'idea del principio di inversione di dipendenza è correlata alla distinzione tra ADT e oggetti? (Vedi la pagina di Wikipedia o Gli oggetti parlanti: una storia sulla programmazione orientata ai messaggi ) Anche se sono nuovo del concetto, capisco che implica l'aggiunta di interfacce tra "livelli" di un programma (vedi diagramma di pagina di Wikipedia).

  4. Fornire eventuali altri esempi / chiarimenti sulla distinzione tra oggetti e ADT, se lo si desidera.

¹ Questo documento (pubblicato nel 2013) è di facile lettura e riassume il documento di Cook del 2009 con esempi in Java. Consiglio vivamente almeno di scremarlo, non per rispondere a questa domanda, ma solo perché è una buona carta.


5
Interessante, ma per favore prova a limitarti a una domanda per post.
Raffaello

sembra in qualche modo una distinzione / dibattito accademico (sottile?). apparentemente gli ADT sono quasi oggetti, ad esempio in Java e altri moderni linguaggi OOP. in molti linguaggi OOP, l'astrazione è considerata come il modo in cui gli oggetti modellano (in modo limitato / focalizzato) il mondo reale. vedi anche confuso sulla definizione di "astrazione" in OOP , Ingegneria del Software
vzn,

Risposte:


8

Google ha sollevato una domanda simile con una risposta che ritengo molto valida. L'ho citato di seguito.

C'è un'altra distinzione in agguato qui che è spiegata nel saggio Cook che ho collegato.

Gli oggetti non sono l'unico modo per implementare l'astrazione. Non tutto è un oggetto. Gli oggetti implementano qualcosa che alcune persone chiamano astrazione di dati procedurali. I tipi di dati astratti implementano una diversa forma di astrazione.

Una differenza chiave appare quando si considerano metodi / funzioni binari. Con l'astrazione di dati procedurali (oggetti), potresti scrivere qualcosa del genere per un'interfaccia Int set:

interface IntSet {
  void unionWith(IntSet s);
  ...
}

Consideriamo ora due implementazioni di IntSet, ad esempio una supportata da elenchi e una supportata da una struttura ad albero binario più efficiente:

class ListIntSet implements IntSet {
  void unionWith(IntSet s){ ... }
} 
class BSTIntSet implements IntSet {
  void unionWith(IntSet s){ ... }
}

Si noti che unionWith deve accettare un argomento IntSet. Non il tipo più specifico come ListIntSet o BSTIntSet. Ciò significa che l'implementazione di BSTIntSet non può presumere che il suo input sia un BSTIntSet e utilizzare tale fatto per fornire un'implementazione efficiente. (Potrebbe utilizzare alcune informazioni sul tipo di runtime per verificarlo e, se lo è, utilizzare un algoritmo più efficiente, ma potrebbe comunque essere passato a un ListIntSet e dovrà ricorrere a un algoritmo meno efficiente).

Confronta questo con gli ADT, dove puoi scrivere qualcosa di più simile al seguente in un file di firma o di intestazione:

typedef struct IntSetStruct *IntSetType;
void union(IntSetType s1, IntSetType s2);

Programmiamo contro questa interfaccia. In particolare, il tipo viene lasciato astratto. Non puoi sapere di cosa si tratta. Quindi abbiamo un'implementazione BST quindi fornisce un tipo e operazioni concrete:

struct IntSetStruct {
 int value;
 struct IntSetStruct* left;
 struct IntSetStruct* right;
}

void union(IntSetType s1, IntSetType s2){ ... }

Ora Union in realtà conosce le rappresentazioni concrete sia di s1 che di s2, quindi può sfruttarlo per un'implementazione efficiente. Possiamo anche scrivere una implementazione supportata da un elenco e scegliere invece di collegarci con quella.

Ho scritto la sintassi C (ish), ma dovresti guardare ad esempio ML standard per i tipi di dati astratti fatti correttamente (dove puoi ad esempio usare più di un'implementazione di un ADT nello stesso programma approssimativamente qualificando i tipi: BSTImpl. IntSetStruct e ListImpl.IntSetStruct, diciamo)

Il contrario è che l'astrazione (oggetti) procedurale dei dati consente di introdurre facilmente nuove implementazioni che funzionano con quelle precedenti. ad esempio puoi scrivere la tua implementazione LoggingIntSet personalizzata e unirla a un BSTIntSet. Ma questo è un compromesso: perdi tipi informativi per i metodi binari! Spesso si finisce per esporre più funzionalità e dettagli di implementazione nella propria interfaccia di quanto si farebbe con un'implementazione ADT. Ora mi sento come se stessi solo scrivendo nuovamente il saggio di Cook, quindi davvero, leggilo!

Vorrei aggiungere un esempio a questo.

Cook suggerisce che un esempio di un tipo di dati astratto è un modulo in C. In effetti, i moduli in C implicano il nascondere informazioni, poiché ci sono funzioni pubbliche che vengono esportate attraverso un file di intestazione e funzioni statiche (private) che non lo fanno. Inoltre, spesso ci sono costruttori (ad esempio list_new ()) e osservatori (ad esempio list_getListHead ()).

Un punto chiave di ciò che rende, diciamo, un modulo elenco chiamato LIST_MODULE_SINGLY_LINKED un ADT è che le funzioni del modulo (ad esempio list_getListHead ()) presuppongono che i dati immessi siano stati creati dal costruttore di LIST_MODULE_SINGLY_LINKED, al contrario di qualsiasi "equivalente "implementazione di un elenco (ad es. LIST_MODULE_DYNAMIC_ARRAY). Ciò significa che le funzioni di LIST_MODULE_SINGLY_LINKED possono assumere, nella loro implementazione, una rappresentazione particolare (ad esempio un elenco collegato singolarmente).

LIST_MODULE_SINGLY_LINKED non può interagire con LIST_MODULE_DYNAMIC_ARRAY perché non possiamo alimentare i dati creati, diciamo con il costruttore di LIST_MODULE_DYNAMIC_ARRAY, all'osservatore di LIST_MODULE_SINGLY_LINKED perché un LIST_MODE presuppone un oggetto

Questo è analogo a un modo in cui due diversi gruppi dall'algebra astratta non possono interagire (cioè, non puoi prendere il prodotto di un elemento di un gruppo con un elemento di un altro gruppo). Questo perché i gruppi assumono la proprietà di chiusura del gruppo (il prodotto degli elementi in un gruppo deve essere nel gruppo). Tuttavia, se possiamo dimostrare che due diversi gruppi sono in realtà sottogruppi di un altro gruppo G, allora possiamo usare il prodotto di G per aggiungere due elementi, uno per ciascuno dei due gruppi.

Confronto tra ADT e oggetti

  • Cook lega la differenza tra ADT e oggetti parzialmente al problema dell'espressione. In parole povere, gli ADT sono associati a funzioni generiche che sono spesso implementate in linguaggi di programmazione funzionale, mentre gli oggetti sono accoppiati a "oggetti" Java a cui si accede tramite interfacce. Ai fini di questo testo, una funzione generica è una funzione che accetta alcuni argomenti ARGS e un tipo TYPE (pre-condizione); in base a TIPO seleziona la funzione appropriata e la valuta con ARGS (post-condizione). Sia le funzioni generiche che gli oggetti implementano il polimorfismo, ma con funzioni generiche, il programmatore CONOSCE quale funzione sarà eseguita dalla funzione generica senza guardare il codice della funzione generica. Con gli oggetti d'altra parte, il programmatore non sa come l'oggetto gestirà gli argomenti, a meno che i programmatori non guardino il codice dell'oggetto.

  • Di solito si pensa al problema dell'espressione in termini di "ho molte rappresentazioni?" vs. "ho molte funzioni con poca rappresentazione". Nel primo caso si dovrebbe organizzare il codice in base alla rappresentazione (come è più comune, specialmente in Java). Nel secondo caso si dovrebbe organizzare il codice in base alle funzioni (ovvero avere un'unica funzione generica gestire più rappresentazioni).

  • Se organizzi il tuo codice in base alla rappresentazione, quindi, se vuoi aggiungere funzionalità extra, sei costretto ad aggiungere la funzionalità a ogni rappresentazione dell'oggetto; in questo senso aggiungere funzionalità non è "additivo". Se organizzi il tuo codice in base alla funzionalità, quindi, se desideri aggiungere una rappresentazione aggiuntiva, sei costretto ad aggiungere la rappresentazione a ogni oggetto; in questo senso aggiungendo rappresentazioni non "additive".

Vantaggio degli ADT sugli oggetti

  • L'aggiunta di funzionalità è additiva

  • Possibile sfruttare la conoscenza della rappresentazione di un ADT per le prestazioni, o per dimostrare che l'ADT garantirà un certo postcondizionamento dato un prerequisito. Ciò significa che la programmazione con gli ADT consiste nel fare le cose giuste nel giusto ordine (concatenando pre-condizioni e post-condizioni verso una condizione post "obiettivo").

Vantaggi degli oggetti rispetto agli ADT

  • Aggiunta di rappresentazioni in additivo

  • Gli oggetti possono interagire

  • È possibile specificare le condizioni pre / post per un oggetto e unirle insieme come nel caso degli ADT. In questo caso, i vantaggi degli oggetti sono che (1) è facile cambiare le rappresentazioni senza cambiare l'interfaccia e (2) gli oggetti possono interagire. Tuttavia, ciò sconfigge lo scopo di OOP nel senso di smalltalk. (vedere la sezione "La versione di OOP di Alan Kay)

L'invio dinamico è la chiave per OOP

Dovrebbe essere evidente ora che l'invio dinamico (ovvero l'associazione tardiva) è essenziale per la programmazione orientata agli oggetti. Questo è così che è possibile definire le procedure in modo generico, che non assume una rappresentazione particolare. Per essere concreti - la programmazione orientata agli oggetti è facile in Python, perché è possibile programmare i metodi di un oggetto in un modo che non assume una rappresentazione particolare. Ecco perché python non ha bisogno di interfacce come Java.

In Java, le classi sono ADT. tuttavia, una classe a cui si accede tramite l'interfaccia implementata è un oggetto.

Addendum: la versione di OOP di Alan Kay

Alan Kay si riferiva esplicitamente agli oggetti come "famiglie di algebre", e Cook suggerisce che un ADT è un'algebra. Quindi Kay probabilmente intendeva dire che un oggetto è una famiglia di ADT. Cioè, un oggetto è la raccolta di tutte le classi che soddisfano un'interfaccia Java.

Tuttavia, l'immagine degli oggetti dipinti da Cook è molto più restrittiva della visione di Alan Kay. Voleva che gli oggetti si comportassero come computer in una rete o come cellule biologiche. L'idea era di applicare il principio del minimo impegno nella programmazione, in modo che sia facile cambiare i livelli di basso livello di un ADT una volta che i livelli di alto livello sono stati costruiti usando loro. Tenendo presente questa immagine, le interfacce Java sono troppo restrittive perché non consentono a un oggetto di interpretare il significato di un messaggio o addirittura di ignorarlo completamente.

In sintesi, l'idea chiave degli oggetti, per Kay - non è che siano una famiglia di algebre (come sottolineato da Cook). Piuttosto, l'idea chiave di Kay era applicare un modello che funzionasse nel grande (computer in una rete) al piccolo (oggetti in un programma).

modifica: un altro chiarimento sulla versione di Kay di OOP: lo scopo degli oggetti è avvicinarsi a un ideale dichiarativo. Dovremmo dire all'oggetto cosa fare, non dirlo come tramite lo stato di micromanaging, come è consuetudine con la programmazione procedurale e gli ADT. Maggiori informazioni possono essere trovate qui , qui , qui e qui .

edit: ho trovato molto, molto buona esposizione della definizione di Alan Kay di OOP qui .


3

Se si osservano i sostenitori di ADT, considerano un ADT quello che OOP chiamerebbe una classe (stato interno, privato; un insieme limitato di operazioni consentite), ma non viene considerata alcuna relazione tra classi (nessuna eredità, in sostanza). Il punto è invece che lo stesso comportamento può essere ottenuto con implementazioni diverse. Ad esempio, un set può essere implementato come un elenco, elementi in un array o hashtable o una sorta di albero.


2

L'ho sempre capito così:

  1. Un ADT è un'interfaccia: è solo una raccolta di metodi, le loro firme di tipo, possibilmente con condizioni pre e post.

  2. Una classe può implementare uno o più ADT, fornendo implementazioni effettive per i metodi specificati nell'ADT.

  3. Un oggetto è un'istanza di una classe, con la propria copia di qualsiasi variabile non statica.

È possibile che in letteratura le distinzioni siano diverse, ma questa è la terminologia "standard" che ascolterai nell'informatica.

Ad esempio, in Java, Collectionè un ADT, ArrayListè una classe e puoi creare un ArrayListoggetto con l' newoperatore.

Per quanto riguarda l'affermazione che gli ADT di solito hanno una sola implementazione, spesso non è così. Ad esempio, è possibile che tu voglia utilizzare dizionari sia basati su albero che basati su hash nel tuo programma, a seconda di ciò che stai memorizzando. Condividono un ADT, ma utilizzano diverse implementazioni.


1
Dal punto di vista della programmazione funzionale, gli ADT non hanno alcune restrizioni che le classi in generale non hanno?
Raffaello

@Raphael Ti piace cosa?
jmite,

1
Questa è una visione comune di ADT ed è un'approssimazione ragionevole. Tuttavia, a quanto ho capito, gli ADT come considerati nella letteratura PL e come definiti formalmente hanno in realtà un significato un po 'più specifico. Un ADT è una specifica di un tipo di struttura di dati: non come viene implementato o come i dati sono rappresentati, ma l'interfaccia con esso (che tipo di operazioni possono essere eseguite?) E il comportamento / semantica di ciascuna di tali operazioni. Quindi non è solo un'interfaccia Java (un elenco di metodi con firme di tipo), ma anche una specifica del loro comportamento.
DW

1
Ad esempio, la mia impressione è che l' Collectioninterfaccia Java non sia un ADT. Fornisce un elenco di metodi ma non specifica la loro semantica. Fornisce la semantica di un set? un multiset (borsa)? un elenco ordinato? Questo è rimasto non specificato. Quindi non sono sicuro che conti come un ADT. Questa è la mia impressione, ma è del tutto possibile che la mia comprensione possa essere sbagliata ...
DW

Nelle diapositive delle lezioni a cui ho collegato, una classe Java (nemmeno un'interfaccia!) È considerata un ADT, poiché una classe ha sia parti private che pubbliche (presumo che parte della classe sarebbe specificata in modo informale ma non ne sono sicuro) . D'altra parte, una classe a cui si accede tramite un'interfaccia è considerata un oggetto, con i metodi definiti dall'interfaccia come "messaggi" (intenzioni di alto livello). Quando gli oggetti parlano tra loro attraverso le intenzioni, diverse implementazioni di un oggetto possono "parlare" tra loro.
LMZ,
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.