Come configurare smartphone e PC. Portale informativo
  • casa
  • Notizia
  • Caratteristiche della programmazione orientata agli oggetti. Principi di programmazione orientata agli oggetti

Caratteristiche della programmazione orientata agli oggetti. Principi di programmazione orientata agli oggetti

Paradigmi di programmazione

Programmazione orientata agli oggetti (OOP)- una metodologia di programmazione basata sulla rappresentazione di un programma come una raccolta di oggetti, ciascuno dei quali è un'istanza di una classe specifica, e le classi formano una gerarchia di ereditarietà.

È necessario notare le seguenti parti importanti di questa definizione: 1) la programmazione orientata agli oggetti utilizza oggetti anziché algoritmi come principali elementi logici; 2) ogni oggetto è un'istanza di una certa classe; 3) le classi formano gerarchie. Un programma è considerato orientato agli oggetti solo se vengono soddisfatti tutti e tre i requisiti specificati. In particolare, la programmazione che non utilizza l'ereditarietà non è detta orientata agli oggetti, ma programmazione con tipi di dati astratti.

YouTube enciclopedico

    1 / 5

    ✪ Programmazione ad oggetti nel 2019

    ✪ Progettazione orientata agli oggetti Parte 1 - Come vengono progettate le classi

    ✪ Principi base della programmazione ad oggetti. Cos'è l'OOP e perché è necessaria?

    ✪ Nozioni di base sull'OOP in C++

    ✪ Programmazione orientata agli oggetti. Classi e oggetti. Lezione 3

    Sottotitoli

Concetti basilari

Astrazione dei dati Astrazione significa isolare le informazioni significative ed escludere dalla considerazione quelle non importanti. L'OOP considera solo l'astrazione dei dati (spesso chiamandola semplicemente “astrazione”), ovvero un insieme di caratteristiche significative di un oggetto accessibile al resto del programma. Incapsulamento L'incapsulamento è una proprietà di sistema che consente di combinare dati e metodi che funzionano con essi in una classe. Alcuni linguaggi (come C++, Java o Ruby) equiparano l'incapsulamento all'occultamento, ma altri (Smalltalk, Eiffel, OCaml) distinguono tra questi concetti. Ereditarietà L'ereditarietà è una proprietà di sistema che consente di descrivere una nuova classe basata su una esistente con funzionalità parzialmente o completamente prese in prestito. La classe da cui deriva l'ereditarietà è chiamata base, genitore o superclasse. Una nuova classe è una classe discendente, erede, figlia o derivata. Polimorfismo del sottotipo Il polimorfismo del sottotipo (in OOP chiamato semplicemente “polimorfismo”) è una proprietà di sistema che consente di utilizzare oggetti con la stessa interfaccia senza informazioni sul tipo e sulla struttura interna dell'oggetto. Un altro tipo di polimorfismo - parametrico - nell'OOP è chiamato programmazione generalizzata. Classe Una classe è un tipo di dati universale e complesso, costituito da un insieme tematicamente unificato di "campi" (variabili di tipo più elementare) e "metodi" (funzioni per lavorare con questi campi), ovvero è un modello di un entità informativa con interfacce interne ed esterne per il funzionamento dei suoi contenuti (valori di campo). In particolare, le classi utilizzano ampiamente blocchi speciali di uno o più spesso due metodi accoppiati responsabili di operazioni elementari con un campo specifico (l'interfaccia per l'assegnazione e la lettura di un valore), che simulano l'accesso diretto al campo. Questi blocchi sono chiamati "proprietà" e hanno quasi lo stesso nome specifico del loro campo (ad esempio, il nome del campo può iniziare con una lettera minuscola, mentre il nome della proprietà può iniziare con una lettera maiuscola). Un'altra manifestazione della natura dell'interfaccia di una classe è che quando si copia la variabile corrispondente tramite l'assegnazione, viene copiata solo l'interfaccia, ma non i dati stessi, ovvero la classe è un tipo di dati di riferimento. Una variabile oggetto di un tipo specificato da una classe è chiamata istanza di quella classe. Inoltre, in alcuni sistemi runtime, una classe può anche essere rappresentata da qualche oggetto durante l'esecuzione del programma attraverso l'identificazione dinamica del tipo di dati. In genere, le classi sono sviluppate in modo tale da garantire l'integrità dei dati dell'oggetto, nonché un'interfaccia comoda e semplice, coerente con la natura dell'oggetto e l'attività da risolvere. A sua volta, l'integrità dell'area tematica degli oggetti e delle loro interfacce, nonché la comodità del loro design, sono garantite dall'ereditarietà. Oggetto Un'entità nello spazio degli indirizzi di un sistema informatico che appare quando viene creata un'istanza di una classe (ad esempio, dopo aver eseguito i risultati della compilazione e collegato il codice sorgente per l'esecuzione).

Classificazione dei sottotipi di OOP

Luca Cardelli e Martin Abadie hanno costruito una logica teorica per l'OOP e una classificazione basata su questa logica. Notano che i concetti e le categorie da loro identificati non si trovano insieme in tutti i linguaggi orientati agli oggetti; la maggior parte dei linguaggi supporta solo sottoinsiemi della teoria, e talvolta anche deviazioni peculiari da essa.

Le differenze più evidenti nella manifestazione degli indicatori di qualità tra lingue di diverso tipo:

  • Nei linguaggi tradizionali, i principi dichiarati mirano ad aumentare il tasso di riutilizzo del codice, che inizialmente è basso per la programmazione imperativa. Nelle applicazioni tipizzate polimorficamente, l'uso dei concetti OOP, al contrario, significa la sua evidente riduzione dovuta al passaggio dal polimorfismo parametrico al polimorfismo ad hoc. I linguaggi tipizzati dinamicamente (Smalltalk, Python, Ruby) utilizzano questi principi per organizzare logicamente un programma e il loro impatto sui tassi di riutilizzo è difficile da prevedere: dipende fortemente dalla disciplina del programmatore. Ad esempio, in CLOS, i metodi multipli sono contemporaneamente funzioni di prima classe, il che consente loro di essere considerati contemporaneamente come relativi quantificati, e come generalizzato (vero polimorfico).
  • Utilizzo dei linguaggi OO tradizionali tipizzazione nominativa, cioè l'ammissibilità del couso di oggetti di classi diverse solo se le relative relazioni tra classi sono esplicitamente indicate. I linguaggi tipizzati polimorficamente sono caratterizzati da tipizzazione strutturale, cioè il coordinamento delle classi tra loro mediante lo stesso meccanismo del coordinamento del numero 5 con il tipo int . Anche le lingue tipizzate dinamicamente occupano qui una posizione intermedia.

Razionale generalizzata invio dinamico(compresa multipla) è stata realizzata da Giuseppe Castagna a metà degli anni '90.

Storia

L'OOP è nata come risultato dello sviluppo dell'ideologia della programmazione procedurale, in cui i dati e le subroutine (procedure, funzioni) per elaborarli non sono formalmente correlati. Per l'ulteriore sviluppo della programmazione orientata agli oggetti sono spesso di grande importanza i concetti di evento (la cosiddetta programmazione orientata agli eventi) e di componente (programmazione dei componenti, COP).

L'interazione degli oggetti avviene attraverso. Il risultato dell'ulteriore sviluppo dell'OOP, a quanto pare, sarà la programmazione orientata agli agenti, dove agenti- parti indipendenti di codice a livello di esecuzione. Gli agenti interagiscono attraverso il cambiamento ambiente, in cui si trovano.

I costrutti linguistici che non sono strutturalmente correlati direttamente agli oggetti, ma che li accompagnano per il loro funzionamento sicuro (situazioni eccezionali, controlli) ed efficiente, sono incapsulati da essi in aspetti (nella programmazione orientata agli aspetti). La programmazione orientata al soggetto estende il concetto di oggetto fornendo un'interazione più unificata e indipendente tra gli oggetti. Potrebbe trattarsi di una fase di transizione tra l'OOP e la programmazione degli agenti in termini di interazione indipendente.

Il primo linguaggio di programmazione a introdurre i concetti di base che sarebbero poi diventati un paradigma è stato Simula, ma il termine "orientamento agli oggetti" non è stato utilizzato nel contesto dell'utilizzo di questo linguaggio. Al momento della sua comparsa, nel 1967, proponeva idee rivoluzionarie: oggetti, classi, metodi virtuali, ecc., Ma tutto ciò non era percepito dai contemporanei come qualcosa di grandioso. In effetti, Simula era un “Algol con classi” che semplificava l’espressione nella programmazione procedurale di molti concetti complessi. Il concetto di classe in Simula può essere completamente definito attraverso la composizione di costrutti Algol (ovvero una classe in Simula è qualcosa di complesso, descritto tramite primitive).

Uno sguardo alla programmazione da una “nuova prospettiva” (diversa da quella procedurale) è stato proposto da Alan Kay e Dan Ingalls nel linguaggio Smalltalk. Qui il concetto di classe è diventato l'idea fondamentale per tutte le altre costruzioni del linguaggio (cioè una classe in Smalltalk è una primitiva con cui vengono descritte le costruzioni più complesse). Fu lui a diventare il primo linguaggio di programmazione orientato agli oggetti ampiamente utilizzato.

Attualmente, il numero di linguaggi di programmazione applicati (elenco dei linguaggi) che implementano il paradigma orientato agli oggetti è il maggiore rispetto ad altri paradigmi. I linguaggi più comuni nell'industria (C++, Delphi, C#, Java, ecc.) incorporano il modello a oggetti Simula. Esempi di linguaggi basati sul modello Smalltalk sono Objective-C, Python, Ruby.

Definizione di OOP e suoi concetti base

Al centro dell'OOP c'è il concetto oggetto. Un oggetto è un'entità a cui possono essere inviati messaggi e che può rispondere utilizzando i suoi dati. Un oggetto è un'istanza di una classe. I dati dell'oggetto sono nascosti dal resto del programma. Nascondere i dati è chiamato incapsulamento.

La presenza dell'incapsulamento è sufficiente per l'obiettività di un linguaggio di programmazione, ma non significa ancora che sia orientato agli oggetti: ciò richiede la presenza dell'ereditarietà.

Ma anche la presenza dell’incapsulamento e dell’ereditarietà non rende un linguaggio di programmazione completamente basato sugli oggetti dal punto di vista dell’OOP. I principali vantaggi dell'OOP compaiono solo quando il linguaggio di programmazione implementa il polimorfismo del sottotipo, ovvero la capacità di elaborare in modo uniforme oggetti con implementazioni diverse, a condizione che esista un'interfaccia comune.

Difficoltà di definizione

L'OOP ha una storia di oltre quarant'anni, ma nonostante ciò non esiste ancora una definizione chiara e generalmente accettata di questa tecnologia. I principi di base stabiliti nei primi linguaggi e sistemi a oggetto hanno subito notevoli modifiche (o distorsioni) e integrazioni in numerose implementazioni dei tempi successivi. Inoltre, a partire dalla metà degli anni '80, il termine "orientato agli oggetti" divenne di moda, di conseguenza gli accadde la stessa cosa che poco prima con il termine "strutturale" (diventato di moda dopo la diffusione della programmazione strutturata). tecnologia) - è diventato artificialmente “attaccato” a qualsiasi nuovo sviluppo per garantirne l'attrattiva. Björn Stroustrup scrisse nel 1988 che la giustificazione per l’“orientamento agli oggetti” di qualcosa, nella maggior parte dei casi, si riduce a un falso sillogismo: “X è buono. L'orientamento agli oggetti è buono. Quindi"X è orientato agli oggetti."

Roger King sosteneva che il suo gatto fosse orientato agli oggetti. Tra gli altri vantaggi, il gatto dimostra un comportamento caratteristico, reagisce ai messaggi, è dotato di reazioni ereditarie e gestisce il proprio stato interno, completamente indipendente.

Tuttavia, la generalità del meccanismo di messaggistica ha anche un altro lato: la trasmissione di messaggi "a tutti gli effetti" richiede un sovraccarico aggiuntivo, il che non è sempre accettabile. Pertanto, molti moderni linguaggi di programmazione orientati agli oggetti utilizzano questo concetto "invio di un messaggio come chiamata al metodo"- gli oggetti hanno metodi accessibili dall'esterno, le cui chiamate garantiscono l'interazione degli oggetti. Questo approccio è implementato in un gran numero di linguaggi di programmazione, tra cui C++, Object Pascal, Java, Oberon-2. Ciò però porta al fatto che i messaggi non sono più oggetti indipendenti e, di conseguenza, non hanno attributi, il che restringe le possibilità di programmazione. Alcuni linguaggi utilizzano una rappresentazione ibrida, mostrando i vantaggi di entrambi gli approcci contemporaneamente, ad esempio CLOS, Python.

Concetto metodi virtuali, supportato da questi e altri linguaggi moderni, è emerso come un mezzo per garantire che i metodi desiderati vengano eseguiti quando si utilizzano variabili polimorfiche, cioè, in sostanza, come un tentativo di estendere la capacità di chiamare metodi per implementare parte delle funzionalità fornite da il meccanismo di elaborazione dei messaggi.

Funzionalità di implementazione

Come accennato in precedenza, nei moderni linguaggi di programmazione orientati agli oggetti, ogni oggetto è un valore appartenente ad una classe specifica. Una classe è un tipo di dati composito dichiarato dal programmatore, contenente:

Campi dati Parametri di un oggetto (ovviamente non tutti, ma solo quelli necessari nel programma), specificando il suo stato (proprietà dell'oggetto dell'area tematica). A volte i campi dati di un oggetto sono chiamati proprietà dell'oggetto, il che può creare confusione. Fisicamente i campi rappresentano valori (variabili, costanti) dichiarati come appartenenti ad una classe. Metodi Procedure e funzioni associate ad una classe. Definiscono le azioni che possono essere eseguite su un oggetto di quel tipo e che l'oggetto stesso può eseguire.

Le classi possono ereditare l'una dall'altra. La classe discendente riceve tutti i campi e i metodi della classe genitore, ma può integrarli con i propri o sovrascrivere quelli esistenti. La maggior parte dei linguaggi di programmazione supporta solo l'ereditarietà singola (una classe può avere solo una classe genitore), solo alcuni consentono l'ereditarietà multipla: la generazione di una classe da due o più classi genitori. L'ereditarietà multipla crea una serie di problemi, sia logici che puramente implementativi, quindi il suo pieno supporto non è diffuso. Invece, negli anni '90, apparve il concetto di interfaccia e cominciò ad essere attivamente introdotto nei linguaggi orientati agli oggetti. Un'interfaccia è una classe senza campi e senza implementazione, contenente solo intestazioni di metodo. Se una classe eredita (o, come si suol dire, implementa) un'interfaccia, deve implementare tutti i suoi metodi. L'uso delle interfacce fornisce un'alternativa relativamente economica all'ereditarietà multipla.

L'interazione degli oggetti nella stragrande maggioranza dei casi è assicurata chiamando i rispettivi metodi.

L'incapsulamento viene fornito nei seguenti modi:

Controllo degli accessi Poiché i metodi di classe possono essere puramente interni, fornendo la logica di funzionamento dell'oggetto, o esterni, con l'aiuto dei quali gli oggetti interagiscono, è necessario garantire la segretezza dei primi rendendo accessibili i secondi dall'esterno. Per fare ciò, nei linguaggi vengono introdotti speciali costrutti sintattici che definiscono esplicitamente l'ambito di ciascun membro della classe. Tradizionalmente, si tratta di modificatori pubblici, protetti e privati, che denotano, rispettivamente, membri pubblici della classe, membri della classe accessibili all'interno della classe e dalle classi discendenti e membri nascosti accessibili solo all'interno della classe. La nomenclatura specifica dei modificatori e il loro significato esatto variano da una lingua all'altra. Metodi di accesso I campi delle classi in generale non dovrebbero essere accessibili dall'esterno, poiché tale accesso consentirebbe modifiche arbitrarie allo stato interno degli oggetti. Pertanto, i campi vengono solitamente dichiarati nascosti (o il linguaggio generalmente non consente l'accesso ai campi della classe dall'esterno) e vengono utilizzati metodi speciali chiamati metodi di accesso per accedere ai dati contenuti nei campi. Tali metodi restituiscono il valore di un particolare campo o scrivono un nuovo valore in questo campo. Durante la scrittura, l'accessore può verificare la validità del valore che si sta scrivendo e, se necessario, eseguire altre manipolazioni sui dati dell'oggetto in modo che rimangano corretti (coerenti internamente). I metodi di accesso sono anche chiamati accessor (dall'inglese access - access) e individualmente - getter (inglese get - lettura) e setter (inglese set - scrittura). Proprietà di un oggetto Pseudofield che può essere letto e/o scritto. Le proprietà assomigliano a campi e vengono utilizzate allo stesso modo dei campi disponibili (con alcune eccezioni), ma in realtà chiamano metodi di accesso quando vi si accede. Pertanto, le proprietà possono essere considerate come campi dati “intelligenti” che accompagnano l'accesso ai dati interni di un oggetto con alcune azioni aggiuntive (ad esempio, quando un cambiamento nelle coordinate di un oggetto è accompagnato dal suo ridisegno in una nuova posizione). Le proprietà, infatti, non sono altro che zucchero sintattico, poiché non aggiungono nuove funzionalità, ma nascondono solo la chiamata ai metodi di accesso. L'implementazione linguistica specifica delle proprietà può variare. Ad esempio, una dichiarazione di proprietà contiene direttamente il codice di accesso che viene chiamato solo quando si lavora con le proprietà, ovvero non richiede metodi di accesso separati che possono essere chiamati direttamente. In Delphi, una dichiarazione di proprietà contiene solo i nomi dei metodi accessori che dovrebbero essere chiamati quando si accede al campo. I metodi di accesso stessi sono metodi regolari con alcuni requisiti di firma aggiuntivi.

Il polimorfismo viene implementato introducendo nel linguaggio delle regole secondo le quali ad una variabile di tipo “classe” può essere assegnato un oggetto di qualsiasi classe discendente della sua classe.

Progettazione di programmi in generale

L'OOP si concentra sullo sviluppo di sistemi software di grandi dimensioni sviluppati da un team di programmatori (possibilmente piuttosto numeroso). La progettazione del sistema nel suo insieme, la creazione dei singoli componenti e la loro integrazione nel prodotto finale vengono spesso eseguite da persone diverse e non esiste un solo specialista che sappia tutto del progetto.

La progettazione orientata agli oggetti si concentra nel descrivere la struttura del sistema progettato (priorità rispetto alla descrizione del suo comportamento, rispetto alla programmazione funzionale), cioè nel rispondere a due domande principali:

  • Da quali parti è composto il sistema?;
  • Qual è la responsabilità di ciascuna delle sue parti?.

L'assegnazione delle parti viene effettuata in modo tale che ciascuna abbia un volume minimo e un insieme di funzioni (responsabilità) ben definite e allo stesso tempo interagisca il meno possibile con le altre parti.

Ulteriori chiarimenti portano all'identificazione di frammenti più piccoli della descrizione. Man mano che la descrizione e la definizione di responsabilità diventano più dettagliate, vengono rivelati i dati che devono essere archiviati e la presenza di agenti simili nel comportamento, che diventano candidati per l'implementazione sotto forma di classi con antenati comuni. Dopo aver individuato i componenti e definito le interfacce tra gli stessi, l'implementazione di ciascun componente può essere effettuata in modo quasi indipendente dagli altri (ovviamente soggetto all'appropriata disciplina tecnologica).

La corretta costruzione della gerarchia delle classi è di grande importanza. Uno dei problemi ben noti dei grandi sistemi realizzati utilizzando la tecnologia OOP è il cosiddetto problema della fragilità delle classi base. Sta nel fatto che nelle fasi successive dello sviluppo, quando viene costruita la gerarchia delle classi e sulla sua base è stata sviluppata una grande quantità di codice, risulta difficile o addirittura impossibile apportare modifiche al codice classi base della gerarchia (da cui derivano tutte o molte classi operanti nel sistema). Anche se le modifiche apportate non influiscono sull'interfaccia della classe base, la modifica del suo comportamento può influire sulle classi discendenti in modi imprevedibili. Nel caso di un sistema di grandi dimensioni, lo sviluppatore della classe base semplicemente non è in grado di prevedere le conseguenze dei cambiamenti, non sa nemmeno come viene utilizzata esattamente la classe base e su quali caratteristiche del suo comportamento si basa il corretto funzionamento delle classi discendenti. dipende.

Varie metodologie OOP

La programmazione dei componenti è la fase successiva nello sviluppo dell'OOP; il prototipo e la programmazione orientata alla classe sono approcci diversi alla creazione di un programma che può essere combinato, con i propri vantaggi e svantaggi.

Programmazione dei componenti

La programmazione orientata ai componenti è una sorta di “sovrastruttura” rispetto all’OOP, un insieme di regole e restrizioni volte a costruire sistemi software di grandi dimensioni e in via di sviluppo con una lunga durata. Un sistema software in questa metodologia è un insieme di componenti con interfacce ben definite. Le modifiche ad un sistema esistente vengono apportate creando nuovi componenti in aggiunta o in sostituzione di quelli precedentemente esistenti. Quando si creano nuovi componenti basati su quelli creati in precedenza, è vietato l'uso dell'ereditarietà dell'implementazione: il nuovo componente può ereditare solo le interfacce di quello di base. In questo modo, la programmazione dei componenti evita il problema della fragilità della classe base.

Programmazione del prototipo

La programmazione dei prototipi, pur mantenendo alcune delle caratteristiche dell'OOP, ha abbandonato i concetti di base di classe ed ereditarietà.

  • Un prototipo è un oggetto campione, a immagine e somiglianza del quale vengono creati altri oggetti. Gli oggetti copia possono mantenere una connessione con l'oggetto genitore, ereditando automaticamente le modifiche al prototipo; questa funzionalità è definita all'interno di una lingua specifica.
  • Invece di un meccanismo per descrivere classi e generare istanze, il linguaggio fornisce un meccanismo per creare un oggetto (specificando un insieme di campi e metodi che l'oggetto deve avere) e un meccanismo per clonare oggetti.
  • Ogni oggetto appena creato è una "istanza senza classe". Ogni oggetto può divenire prototipo- essere utilizzato per creare un nuovo oggetto utilizzando un'operazione clonazione. Dopo la clonazione è possibile modificare il nuovo oggetto, in particolare aggiungere nuovi campi e metodi.
  • Un oggetto clonato diventa una copia completa del prototipo, memorizzando tutti i valori dei suoi campi e duplicando i suoi metodi, oppure conserva un riferimento al prototipo senza includere i campi e i metodi clonati finché non vengono modificati. In quest'ultimo caso, l'ambiente runtime fornisce un meccanismo delegazione- se, quando si accede a un oggetto, esso stesso non contiene il metodo o il campo dati richiesto, la chiamata viene passata al prototipo, da esso, se necessario, più avanti lungo la catena.

Programmazione orientata alla classe

La programmazione orientata alla classe è una programmazione incentrata sui dati, in cui dati e comportamento sono indissolubilmente legati. Insieme, dati e comportamento costituiscono una classe. Di conseguenza, nei linguaggi basati sul concetto di "classe", tutti gli oggetti sono divisi in due tipi principali: classi e istanze. Una classe definisce struttura e funzionalità (comportamento) che sono le stesse per tutte le istanze di quella classe. Un'istanza è un supporto dati, ovvero ha uno stato che cambia in base al comportamento specificato dalla classe. Nei linguaggi orientati alle classi, una nuova istanza viene creata tramite una chiamata al costruttore della classe (possibilmente con un insieme di parametri). L'istanza risultante ha la struttura e il comportamento codificati dalla sua classe.

Prestazioni del programma oggetto

Gradi Booch sottolinea i seguenti motivi che portano a una riduzione delle prestazioni del programma a causa dell'uso di strumenti orientati agli oggetti:

Collegamento dinamico dei metodi Garantire il comportamento polimorfico degli oggetti porta alla necessità di collegare i metodi chiamati dal programma (ovvero determinare quale metodo specifico verrà chiamato) non in fase di compilazione, ma durante l'esecuzione del programma, il che richiede tempo aggiuntivo. Tuttavia, l'associazione dinamica è effettivamente richiesta per non più del 20% delle chiamate, ma alcuni linguaggi OOP la utilizzano sempre. La notevole profondità di astrazione dello sviluppo OOP porta spesso alla creazione di applicazioni “multistrato”, in cui l’esecuzione di un oggetto di un’azione richiesta è ridotta a molte chiamate a oggetti di livello inferiore. In un'applicazione di questo tipo sono presenti numerose chiamate e ritorni di metodo, che influiscono naturalmente sulle prestazioni. L'ereditarietà "confonde" il codice: il codice relativo alle classi "finali" della gerarchia di ereditarietà, che di solito vengono utilizzate direttamente dal programma, si trova non solo in queste classi stesse, ma anche nelle loro classi antenati. I metodi appartenenti alla stessa classe sono in realtà descritti in classi diverse. Ciò porta a due cose spiacevoli:

  • La velocità di traduzione diminuisce, poiché il linker deve caricare le descrizioni di tutte le classi nella gerarchia.
  • Le prestazioni del programma in un sistema con memoria paginata diminuiscono: poiché i metodi di una classe si trovano fisicamente in punti diversi nel codice, lontani l'uno dall'altro, quando si eseguono frammenti di programma che accedono attivamente ai metodi ereditati, il sistema è costretto a eseguire frequenti cambi di pagina.
L'incapsulamento riduce la velocità di accesso ai dati.Il divieto di accesso diretto ai campi della classe dall'esterno porta alla necessità di creare e utilizzare metodi di accesso. Sono previsti costi aggiuntivi associati alla scrittura, alla compilazione e all'esecuzione dei metodi di accesso. Creazione e distruzione dinamica di oggetti Gli oggetti creati dinamicamente, di regola, vengono allocati nell'heap, il che è meno efficiente che posizionarli nello stack e, inoltre, allocare staticamente memoria per loro in fase di compilazione.

Nonostante queste carenze, Booch sostiene che i vantaggi derivanti dall’utilizzo dell’OOP sono maggiori. Inoltre, l'aumento della produttività dovuto ad una migliore organizzazione del codice OOP, secondo lui, in alcuni casi compensa i costi generali aggiuntivi legati all'organizzazione del funzionamento del programma. Puoi anche notare che molti effetti di degrado delle prestazioni possono essere attenuati o addirittura eliminati completamente grazie all'ottimizzazione del codice di alta qualità da parte del compilatore. Ad esempio, la suddetta riduzione della velocità di accesso ai campi della classe dovuta all'uso dei metodi di accesso viene eliminata se il compilatore utilizza la sostituzione in linea invece di chiamare il metodo di accesso (i compilatori moderni lo fanno con sicurezza).

Critica all'OOP

Nonostante alcune critiche all’OOP, questo è il paradigma attualmente utilizzato nella stragrande maggioranza dei progetti industriali. Tuttavia non si può dare per scontato che l’OOP sia la migliore tecnica di programmazione in tutti i casi.

Critiche all’OLP:

  • È stato dimostrato che non vi è alcuna differenza significativa nella produttività dello sviluppo software tra l'OOP e l'approccio procedurale.
  • Christopher Date sottolinea l'impossibilità di confrontare l'OOP e altre tecnologie in gran parte a causa della mancanza di una definizione rigorosa e generalmente accettata di OOP.
  • Alexander Stepanov ha sottolineato in una delle sue interviste che l'OOP è “metodologicamente scorretto” e che “... l'OOP è praticamente la stessa bufala dell'intelligenza artificiale...”.
  • Frederick Brooks sottolinea che la parte più difficile della creazione di software è "... la specifica, la progettazione e il test dei costrutti concettuali, non il lavoro di espressione di tali costrutti concettuali...". L'OOP (insieme a tecnologie come l'intelligenza artificiale, la verifica dei programmi, la programmazione automatica, la programmazione grafica, i sistemi esperti, ecc.), a suo avviso, non è una "proiettile d'argento" che potrebbe ridurre la complessità dello sviluppo di sistemi software di un ordine di grandezza. Secondo Brooks, “...OOP riduce solo la complessità introdotta nell'espressione del design. Il design rimane complesso per natura...”
  • Edsger Dijkstra ha sottolineato: “… ciò che la società nella maggior parte dei casi chiede è un elisir per tutte le malattie. Naturalmente, l '"elisir" ha nomi molto impressionanti, altrimenti sarà molto difficile vendere qualcosa: "Analisi e progettazione strutturale", "Ingegneria del software", "Modelli di maturità", "Sistemi informativi gestionali", "Ambienti integrati" supporto al progetto ", "Orientamento agli oggetti", "Reingegnerizzazione dei processi aziendali...".
  • Niklaus Wirth ritiene che l'OOP non sia altro che una banale sovrastruttura sulla programmazione strutturata, e l'esagerazione della sua importanza, espressa, tra le altre cose, nell'inclusione di strumenti "orientati agli oggetti" sempre più alla moda nei linguaggi di programmazione, danneggia la qualità del software in fase di sviluppo.
  • Patrick Killelia, nel suo libro “Tuning a Web Server”, ha scritto: “...OOP ti offre molti modi per rallentare i tuoi programmi...”.
  • Un noto articolo di revisione sui problemi nella moderna programmazione OOP elenca alcuni tipici problemi OOP [ ] .
  • Nel folklore della programmazione, critica all’approccio orientato agli oggetti rispetto all’approccio funzionale utilizzando la metafora “ Regni sostantivi" da un saggio di Steve Yeaggie.

Se proviamo a classificare le critiche all'OOP, possiamo evidenziare diversi aspetti critici di questo approccio alla programmazione.

Critica della pubblicità OOP L'idea della programmazione a oggetti come una sorta di approccio onnipotente che elimina magicamente la complessità della programmazione viene criticata, esplicitamente dichiarata o implicita nei lavori di alcuni propagandisti OOP, così come nei materiali pubblicitari per "orientati agli oggetti" strumenti di sviluppo. Come molti hanno notato, inclusi Brooks e Dijkstra menzionati sopra, "non esiste una soluzione miracolosa": indipendentemente dal paradigma di programmazione a cui aderisce lo sviluppatore, la creazione di un sistema software complesso e non banale comporta sempre un investimento significativo di risorse intellettuali e tempo. Tra gli specialisti più qualificati nel campo dell'OOP, nessuno, di regola, nega la fondatezza di critiche di questo tipo. Contestare l'efficienza dello sviluppo OOP I critici contestano l'idea che lo sviluppo di programmi orientati agli oggetti richieda meno risorse o si traduca in software migliore. Viene effettuato un confronto dei costi di sviluppo utilizzando metodi diversi, sulla base del quale si conclude che l'OOP non presenta vantaggi in questa direzione. Data l’estrema difficoltà di confrontare oggettivamente i diversi sviluppi, tali confronti sono, a dir poco, controversi. D’altro canto risulta che le affermazioni sull’efficacia dell’OOP sono altrettanto controverse. Prestazioni dei programmi orientati agli oggetti È stato sottolineato che una serie di "caratteristiche innate" della tecnologia OOP rendono i programmi costruiti sulla sua base tecnicamente meno efficienti rispetto a programmi simili non a oggetti. Senza negare che ci siano effettivamente costi generali aggiuntivi per l'organizzazione del funzionamento dei programmi OOP (vedi la sezione "Prestazioni" sopra), va notato, tuttavia, che l'importanza della penalizzazione della prestazione è spesso esagerata dai critici. Nelle condizioni moderne, quando le capacità tecniche dei computer sono estremamente ampie e in costante crescita, per la maggior parte dei programmi applicativi l'efficienza tecnica risulta essere meno significativa della funzionalità, della velocità di sviluppo e della manutenibilità. Solo per una certa classe molto limitata di programmi (software di sistema integrato, driver di dispositivo, parte di basso livello del software di sistema, software scientifico) le prestazioni rimangono un fattore critico. Critiche alle singole soluzioni tecnologiche nei linguaggi e nelle librerie OOP Queste critiche sono numerose, ma non riguardano l'OOP in quanto tale, ma l'accettabilità e l'applicabilità in casi specifici di determinate implementazioni dei suoi meccanismi. Uno degli oggetti di critica preferiti è il linguaggio C++, che è uno dei linguaggi OOP industriali più diffusi.

Linguaggi orientati agli oggetti

Molti linguaggi moderni sono progettati specificamente per facilitare la programmazione orientata agli oggetti. Tuttavia è bene notare che è possibile applicare le tecniche OOP ad un linguaggio non orientato agli oggetti e viceversa; utilizzare un linguaggio orientato agli oggetti non significa che il codice diventi automaticamente orientato agli oggetti.

Tipicamente, un linguaggio orientato agli oggetti (OOL) contiene il seguente insieme di elementi:

  • Dichiarazione di classi con campi (dati - membri della classe) e metodi (funzioni - membri della classe).
  • Il meccanismo di estensione della classe (ereditarietà) è la generazione di una nuova classe da una esistente con l'inclusione automatica di tutte le caratteristiche dell'implementazione della classe antenata nella composizione della classe discendente. La maggior parte degli OOO supporta solo l'ereditarietà singola.
  • Variabili polimorfiche e parametri di funzioni (metodi), che consentono di assegnare istanze di classi diverse alla stessa variabile.
  • Comportamento polimorfico delle istanze di classi attraverso l'uso di metodi virtuali. In alcuni OY, tutti i metodi di classe sono virtuali.

Alcune lingue aggiungono alcune funzionalità aggiuntive al set minimo specificato. Tra loro:

  • Costruttori, distruttori, finalizzatori;
  • Proprietà (accessori);
  • Indicizzatori;
  • Strumenti per controllare la visibilità dei componenti della classe (interfacce o modificatori di accesso, come public, private, protected, feature, ecc.).

Alcuni linguaggi rispettano pienamente i principi dell'OOP: in essi tutti gli elementi principali sono oggetti con stati e metodi associati. Esempi di tali lingue sono Smalltalk, Eiffel. Esistono linguaggi ibridi che combinano il sottosistema di oggetti nella sua interezza con i sottosistemi di altri paradigmi come "due o più linguaggi in uno", consentendo di combinare modelli di oggetti con altri in un programma e offuscando il confine tra oggetti -orientati e altri paradigmi grazie a capacità non standard che si bilanciano tra OOP e altri paradigmi (come invio multiplo, classi parametriche, capacità di manipolare metodi di classe come oggetti indipendenti, ecc.). Esempi di tali lingue:

Classe (classi) è un tipo di dati definito dall'utente. La classe specifica le proprietà e il comportamento di un oggetto o processo sotto forma di campi dati e funzioni per lavorare con essi.

Una proprietà essenziale di una classe è che i suoi dettagli di implementazione sono nascosti agli utenti della classe dietro l'interfaccia. Pertanto, la classe come modello di un oggetto del mondo reale è una scatola nera, chiusa rispetto al mondo esterno.

L'idea delle classi è il nucleo della programmazione orientata agli oggetti (OOP). I principi di base dell'OOP sono stati sviluppati nei linguaggi Simula-67 e SmallTalk, ma a quel tempo non erano ampiamente utilizzati a causa delle difficoltà di apprendimento e della bassa efficienza di implementazione.

Vengono richiamati valori specifici del tipo di dati “class”. istanze della classe O oggetti (oggetti) .

Vengono chiamate le subroutine che definiscono le operazioni sugli oggetti della classe metodi (metodi). Vengono chiamate le chiamate ai metodi messaggi (messaggi). L'intero insieme di metodi di un oggetto è chiamato protocollo di messaggio o interfaccia dei messaggi (Messaggiointerfaccia) oggetto. Un messaggio deve contenere almeno due parti: l'oggetto specifico a cui deve essere inviato e il nome di un metodo che specifica l'azione richiesta sull'oggetto. Pertanto, il calcolo in un programma orientato agli oggetti è determinato dai messaggi passati da un oggetto all'altro.

Gli oggetti interagiscono tra loro inviando e ricevendo messaggi. Un messaggio è una richiesta di eseguire un'azione, contenente una serie di parametri necessari. Il meccanismo del messaggio viene implementato chiamando le funzioni appropriate. Utilizzando l'OOP, il cosiddetto modello basato sugli eventi è facilmente implementabile, quando i dati sono attivi e controllano la chiamata di un particolare pezzo di codice di programma.

L'OOP è un metodo di programmazione che sviluppa i principi della programmazione strutturata e si basa sulle seguenti astrazioni di dati:

IO. Incapsulamento : combinazione di dati con procedure e funzioni in un unico blocco di codice di programma (i dati e i metodi per lavorare con essi sono considerati campi oggetto).

II. Eredità – trasferimento di metodi e proprietà da antenato a discendente, senza la necessità di scrivere ulteriore codice di programma (presenza di istanze di classi; discendenti, antenati, gerarchia).

III. Polimorfismo – la capacità di modificare proprietà e comportamento di oggetti identici nel significato a seconda della loro tipologia (un unico nome per una determinata azione, che viene eseguita in modo diverso per gli oggetti della gerarchia).

Incapsulamento

Il concetto di incapsulamento è stato utilizzato per la prima volta nei linguaggi che supportano il cosiddetto approccio astratto alla programmazione (ad esempio Modula-2). L'idea principale dell'approccio astratto è nascondere all'utente la struttura delle informazioni su un oggetto, dandogli l'opportunità di ottenere i dati necessari per lavorare con l'oggetto solo attraverso procedure relative a questo oggetto. Questa tecnica può aumentare significativamente l'affidabilità e la portabilità del software sviluppato. L'affidabilità aumenta perché tutte le procedure per lavorare con i dati degli oggetti sono relativamente semplici e trasparenti, il che significa che possono essere sviluppate con una migliore qualità. Quando si modifica la struttura dei dati, è sufficiente rielaborare solo i programmi direttamente correlati all'oggetto e non è necessario modificare i programmi più complessi che utilizzano questo oggetto. Questa circostanza aumenta sia l'affidabilità che la mobilità dei programmi creati.

Eredità

Nella seconda metà degli anni ’80, per molti sviluppatori di software divenne evidente che una delle migliori opportunità per aumentare la propria produttività era riutilizzare i programmi. È chiaro che i tipi di dati astratti, con il loro incapsulamento e controllo di accesso, dovrebbero essere riutilizzabili. Il problema con il riutilizzo dei tipi di dati astratti, in quasi tutti i casi, è che le proprietà e le capacità dei tipi esistenti non sono adatte al nuovo utilizzo. I tipi più vecchi devono essere modificati almeno minimamente. Tali modifiche possono essere difficili da implementare e richiedono che una persona comprenda parte, se non tutto, del codice esistente. Inoltre, in molti casi le modifiche comportano cambiamenti in tutti i programmi client.

Il secondo problema con la programmazione orientata ai dati è che tutte le definizioni di tipi di dati astratti sono indipendenti e allo stesso livello della gerarchia. Ciò spesso impedisce al programma di essere strutturato per adattarsi al dominio problematico. In molti casi, il problema originale contiene categorie di oggetti interconnessi che sono sia eredi degli stessi antenati (cioè situati allo stesso livello gerarchico), sia antenati ed eredi (cioè in relazione a una certa subordinazione insieme).

L'ereditarietà risolve sia i problemi di modifica che derivano dal riutilizzo di un tipo di dati astratto, sia i problemi di organizzazione del programma. Se un nuovo tipo di dati astratti può ereditare i dati e le proprietà funzionali di un tipo esistente, nonché modificare alcune di tali entità e aggiungere nuove entità, il riutilizzo è notevolmente facilitato senza la necessità di apportare modifiche al tipo di dati astratti riutilizzato. I programmatori possono prendere un tipo di dati astratto esistente e modellarlo su un nuovo tipo che soddisfi i nuovi requisiti del problema. Supponiamo che il tuo programma abbia un tipo di dati astratto per array di numeri interi che include un'operazione di ordinamento. Dopo un certo periodo di utilizzo, il programma viene modificato e richiede non solo un tipo di dati astratto per array di numeri interi con un'operazione di ordinamento, ma anche un'operazione per calcolare la media aritmetica per elementi di oggetti che sono array. Poiché la struttura di un array è nascosta in un tipo di dati astratto, senza ereditarietà il tipo deve essere modificato aggiungendo una nuova operazione a quella struttura. Con l'ereditarietà non è necessario modificare un tipo esistente; È possibile descrivere una sottoclasse di tipo esistente che supporti non solo un'operazione di ordinamento, ma anche un'operazione di calcolo della media aritmetica.

Viene richiamata una classe definita ereditando da un'altra classe classe derivata (derivatoclasse) , O sottoclasse (sottoclasse) . Viene chiamata la classe da cui deriva la nuova classe classe genitore (genitoreclasse) , O superclasse (superclasse) .

Nel caso più semplice, una classe eredita tutte le entità (variabili e metodi) della classe genitore. Questa eredità può essere resa più complessa introducendo il controllo dell'accesso alle entità della classe genitore.

Questo controllo di accesso consente al programmatore di nascondere parti del tipo di dati astratto ai client. Questo tipo di controllo dell'accesso è comune nelle classi di linguaggio orientate agli oggetti. Le classi derivate sono un altro tipo di client a cui è possibile concedere o negare l'accesso. Per far fronte a ciò, alcuni linguaggi orientati agli oggetti includono una terza categoria di controllo dell'accesso, spesso chiamata protetta, che viene utilizzata per garantire l'accesso alle classi derivate e negare l'accesso ad altre classi.

Oltre alle entità ereditate, una classe derivata può aggiungere nuove entità e modificare metodi. Un metodo modificato ha lo stesso nome e spesso lo stesso protocollo del metodo di cui è una modifica. Si dice che il nuovo metodo sovrascriva la versione ereditata del metodo, che viene quindi chiamato metodo sottoposto a override. Lo scopo più generale di un metodo di sostituzione è eseguire un'operazione specifica per gli oggetti della classe derivata e non specifica per gli oggetti della classe genitore.

Lo sviluppo di un programma per un sistema orientato agli oggetti inizia con la definizione di una gerarchia di classi che descrive le relazioni tra gli oggetti che verranno inclusi nel programma che risolve il problema. Quanto meglio questa gerarchia di classi corrisponde alla parte del problema, tanto più naturale sarà la soluzione completa.

Lo svantaggio dell'ereditarietà come mezzo per facilitare il riutilizzo del codice è che crea dipendenze tra le classi nella gerarchia di ereditarietà. Ciò mina uno dei vantaggi dei tipi di dati astratti, ovvero la loro reciproca indipendenza. Naturalmente, non tutti i tipi di dati astratti devono essere completamente indipendenti, ma in generale l'indipendenza dei tipi di dati astratti è una delle loro proprietà positive più forti. Tuttavia, aumentare la riusabilità dei tipi di dati astratti senza creare dipendenze tra alcuni di essi può essere difficile, se non del tutto senza speranza.

Polimorfismo

Una terza proprietà dei linguaggi di programmazione orientati agli oggetti è il tipo di polimorfismo fornito dal legame dinamico dei messaggi alle definizioni dei metodi. Questa proprietà è supportata consentendo la definizione di variabili polimorfiche del tipo di una classe genitore che possono riferirsi anche a oggetti di eventuali sottoclassi di quella classe. Una classe genitore può definire un metodo che viene sovrascritto nelle sue sottoclassi. Le operazioni definite da questi metodi sono simili, ma devono essere specificate per ciascuna classe nella gerarchia. Quando un metodo di questo tipo viene chiamato tramite una variabile polimorfica, quella chiamata viene associata dinamicamente a un metodo nella classe corrispondente. Uno degli obiettivi del collegamento dinamico è consentire ai sistemi software di essere estesi più facilmente man mano che vengono sviluppati e mantenuti. Tali programmi possono essere scritti per eseguire operazioni su oggetti di classi personalizzate. Queste operazioni sono personalizzabili nel senso che possono essere applicate a oggetti di qualsiasi classe che derivi dalla stessa classe base.

Informatica in linguaggi orientati agli oggetti

Tutti i calcoli in un linguaggio completamente orientato agli oggetti vengono eseguiti passando un messaggio a un oggetto per chiamare uno dei suoi metodi. La risposta al messaggio è un oggetto che restituisce il risultato dei calcoli eseguiti da questo metodo. L'esecuzione di un programma in un linguaggio orientato agli oggetti può essere descritta come la simulazione di un insieme di computer (oggetti) che interagiscono tra loro utilizzando messaggi. Ogni oggetto è un'astrazione di un computer, nel senso che memorizza dati e fornisce processi per manipolare tali dati. Inoltre, gli oggetti possono inviare e ricevere messaggi. In sostanza, queste sono le proprietà di base di un computer: archiviazione ed elaborazione dei dati, nonché trasmissione e ricezione di messaggi.

L'essenza della programmazione orientata agli oggetti è risolvere i problemi identificando gli oggetti rilevanti del mondo reale e l'elaborazione richiesta per tali oggetti; e successiva modellazione di questi oggetti, dei loro processi e delle necessarie connessioni tra loro.

Libreria dei componenti visivi (VCL)

Delfi contiene un gran numero di classi progettate per lo sviluppo rapido di applicazioni. La libreria è scritta in Object Pascal e ha una connessione diretta con l'ambiente di sviluppo applicativo integrato Delphi.

Tutte le classi VCL si trovano ad un certo livello della gerarchia e del modulo albero (gerarchia) delle classi.

Conoscere l'origine di un oggetto è di grande aiuto per studiarlo, poiché il bambino eredita tutti gli elementi dell'oggetto genitore. Pertanto, se la proprietà Caption appartiene alla classe TControl, anche i suoi discendenti avranno questa proprietà, ad esempio, rispettivamente le classi TButton e TCheckBox e i componenti Button e l'interruttore indipendente CheckBox. Un frammento della gerarchia delle classi con le classi più importanti è mostrato in Fig.

Oltre alla gerarchia delle classi, di grande aiuto nell'apprendimento del sistema di programmazione sono i codici sorgente dei moduli, che si trovano nella directory SOURCE della directory principale di Delphi.

Per volontà del destino, devo leggere un corso speciale sui design pattern all'università. Il corso speciale è obbligatorio, quindi gli studenti che vengono da me sono molto diversi. Naturalmente tra loro ci sono anche programmatori praticanti. Ma sfortunatamente la maggior parte delle persone ha difficoltà anche solo a comprendere i termini base dell’OOP.

Per fare questo, ho provato a spiegare i concetti base dell'OOP (classe, oggetto, interfaccia, astrazione, incapsulamento, ereditarietà e polimorfismo) utilizzando esempi più o meno reali.

La prima parte di seguito riguarda classi, oggetti e interfacce.
La seconda parte illustra l'incapsulamento, il polimorfismo e l'ereditarietà

Concetti base dell'OOP

Classe
Immagina di progettare un'auto. Sai che un'auto deve contenere un motore, sospensioni, due fari, 4 ruote, ecc. Sai anche che la tua auto deve essere in grado di accelerare e rallentare, girare e fare retromarcia. E, soprattutto, sai esattamente come interagiscono il motore e le ruote, secondo quali leggi si muovono l'albero a camme e l'albero motore, nonché come sono progettati i differenziali. Sei sicuro delle tue conoscenze e inizi a progettare.

Descrivi tutte le parti che compongono la tua auto e il modo in cui queste parti interagiscono tra loro. Inoltre, descrivi cosa deve fare l'utente per far frenare l'auto o accendere gli abbaglianti. Il risultato del tuo lavoro sarà uno schizzo. Hai appena sviluppato ciò che viene chiamato in OOP Classe.

Classeè un modo di descrivere un'entità che definisce uno stato e un comportamento che dipende da questo stato, nonché le regole per interagire con questa entità (contratto).

Da un punto di vista della programmazione, una classe può essere considerata come un insieme di dati (campi, attributi, membri della classe) e funzioni per lavorare con essi (metodi).

Dal punto di vista della struttura del programma, una classe è un tipo di dati complesso.

Nel nostro caso, la classe mostrerà un'entità: un'auto. Gli attributi di classe saranno il motore, le sospensioni, la carrozzeria, le quattro ruote, ecc. I metodi della lezione saranno "aprire la porta", "premere il pedale dell'acceleratore" e anche "pompare una porzione di benzina dal serbatoio nel motore". I primi due metodi sono disponibili per l'esecuzione da parte di altre classi (in particolare, la classe “Driver”). Quest'ultimo descrive le interazioni all'interno della classe e non è accessibile all'utente.

D'ora in poi, anche se la parola "utente" sarà associata a Solitario e Microsoft Word, ci riferiremo come utenti ai programmatori che utilizzano la tua classe, incluso te stesso. Chiameremo sviluppatore la persona che è l'autore della classe.

Un oggetto
Hai fatto un ottimo lavoro e le macchine sviluppate secondo i tuoi disegni stanno uscendo dalla catena di montaggio. Eccoli, in file ordinate nel cortile della fabbrica. Ognuno di loro ripete esattamente i tuoi disegni. Tutti i sistemi interagiscono esattamente come li hai progettati. Ma ogni macchina è unica. Hanno tutte numeri di carrozzeria e di motore, ma questi numeri sono tutti diversi, le auto differiscono nel colore e alcune hanno addirittura fusioni invece delle ruote stampate. Queste auto sono essenzialmente oggetti della tua classe.

Oggetto (istanza)è un individuo rappresentativo di una classe che ha uno stato e un comportamento specifici interamente determinati dalla classe.

In termini semplici, un oggetto ha valori di attributi specifici e metodi che operano su tali valori in base alle regole definite nella classe. In questo esempio, se la classe è un’auto astratta del “mondo delle idee”, allora l’oggetto è un’auto concreta parcheggiata sotto le finestre.

Interfaccia
Quando ci avviciniamo a una macchina del caffè o ci mettiamo al volante, iniziamo un’interazione con loro. Solitamente l'interazione avviene utilizzando un determinato insieme di elementi: una fessura per accettare monete, un pulsante per selezionare una bevanda e uno scomparto per l'erogazione di un bicchiere in una macchina da caffè; volante, pedali, leva del cambio in un'auto. C'è sempre un insieme limitato di controlli con cui possiamo interagire.

Interfacciaè un insieme di metodi di classe disponibili per l'utilizzo da parte di altre classi.

Ovviamente l'interfaccia di una classe sarà l'insieme di tutti i suoi metodi pubblici insieme ad un insieme di attributi pubblici. Essenzialmente, un'interfaccia specifica una classe, definendo chiaramente tutte le possibili azioni su di essa.
Un buon esempio di interfaccia è il cruscotto di un'auto, che permette di richiamare modalità come accelerare, frenare, svoltare, cambiare marcia, accendere i fari, ecc. Cioè, tutte le azioni che un'altra classe (nel nostro caso, il conducente) può eseguire quando interagisce con l'auto.

Quando si descrive l'interfaccia di una classe, è molto importante trovare un equilibrio tra flessibilità e semplicità. Una classe con un'interfaccia semplice sarà facile da usare, ma ci saranno problemi che non potrà risolvere. Allo stesso tempo, se l'interfaccia è flessibile, molto probabilmente consisterà in metodi piuttosto complessi con un gran numero di parametri che ti permetteranno di fare molto, ma il suo utilizzo sarà irto di grandi difficoltà e del rischio di creare un errore, qualcosa di confuso

Un esempio di interfaccia semplice potrebbe essere un'auto con cambio automatico. Qualsiasi bionda che abbia completato un corso di guida di due settimane sarà in grado di padroneggiarne l'uso molto rapidamente. D'altra parte, per padroneggiare il controllo di un moderno aereo passeggeri, sono necessari diversi mesi o addirittura anni di duro addestramento. Non vorrei essere a bordo di un Boeing pilotato da qualcuno che ha due settimane di esperienza di volo. D'altra parte, non avrai mai un'auto per decollare e volare da Mosca a Washington.

Java è un linguaggio orientato agli oggetti. Ciò significa che è necessario scrivere programmi Java utilizzando uno stile orientato agli oggetti. E questo stile si basa sull'uso di oggetti e classi nel programma. Proviamo, con l'aiuto di esempi, a capire cosa sono le classi e gli oggetti e come applicare nella pratica i principi di base dell'OOP: astrazione, ereditarietà, polimorfismo e incapsulamento.

Cos'è un oggetto?

Il mondo in cui viviamo è costituito da oggetti. Se ci guardiamo intorno, vedremo che siamo circondati da case, alberi, automobili, mobili, stoviglie, computer. Tutti questi elementi sono oggetti e ciascuno di essi ha una serie di caratteristiche, comportamenti e scopi specifici. Siamo abituati agli oggetti e li usiamo sempre per scopi molto specifici. Ad esempio, se dobbiamo andare al lavoro usiamo l'auto, se vogliamo mangiare usiamo i piatti e se abbiamo bisogno di rilassarci abbiamo bisogno di un comodo divano. Una persona è abituata a pensare in modo obiettivo per risolvere i problemi nella vita di tutti i giorni. Questo era uno dei motivi per utilizzare gli oggetti nella programmazione e questo approccio alla creazione di programmi era chiamato orientato agli oggetti. Facciamo un esempio. Immagina di aver sviluppato un nuovo modello di telefono e di voler avviarne la produzione in serie. Come progettista di telefoni, sai a cosa serve, come funzionerà e da quali parti sarà composto (custodia, microfono, altoparlante, cavi, pulsanti, ecc.). Tuttavia, solo tu sai come collegare queste parti. Tuttavia, non hai intenzione di produrre telefoni personalmente, per questo hai un intero staff di dipendenti. Per non doverti spiegare ogni volta come collegare le parti del telefono e affinché tutti i telefoni in produzione risultino uguali, prima di iniziare a produrli dovrai fare un disegno sotto forma di descrizione della struttura del telefono. In OOP, tale descrizione, disegno, diagramma o modello è chiamato classe, dalla quale viene creato un oggetto quando il programma viene eseguito. Una classe è una descrizione di un oggetto non ancora creato, come un modello generale composto da campi, metodi e un costruttore, e un oggetto è un'istanza di una classe creata sulla base di questa descrizione.

Astrazione

Pensiamo ora a come possiamo passare da un oggetto nel mondo reale a un oggetto in un programma, usando il telefono come esempio. La storia di questo mezzo di comunicazione supera i 100 anni e il telefono moderno, a differenza del suo predecessore del XIX secolo, è un apparecchio molto più complesso. Quando utilizziamo un telefono, non pensiamo alla sua struttura e ai processi che avvengono al suo interno. Utilizziamo semplicemente le funzioni fornite dagli sviluppatori del telefono: pulsanti o touch screen per selezionare un numero ed effettuare chiamate. Una delle prime interfacce telefoniche era una manopola che giravi per effettuare una chiamata. Naturalmente, questo non era molto conveniente. Ciononostante la maniglia ha svolto correttamente la sua funzione. Se guardi il telefono più moderno e il primo in assoluto, puoi immediatamente identificare i dettagli più importanti che sono importanti sia per un dispositivo della fine del XIX secolo che per uno smartphone ultramoderno. Questo significa effettuare una chiamata (comporre un numero) e ricevere una chiamata. In sostanza, questo è ciò che rende un telefono un telefono e non qualcos'altro. Ora abbiamo applicato il principio nell'OOP, evidenziando le caratteristiche e le informazioni più importanti su un oggetto. Questo principio si chiama astrazione. L'astrazione in OOP può anche essere definita come un modo di rappresentare elementi di un problema del mondo reale come oggetti in un programma. L'astrazione è sempre associata alla generalizzazione di alcune informazioni sulle proprietà di oggetti o oggetti, quindi la cosa principale è separare le informazioni significative da quelle insignificanti nel contesto del problema da risolvere. In questo caso possono esserci diversi livelli di astrazione. Proviamo ad applicare il principio di astrazione ai nostri telefoni. Innanzitutto, evidenziamo i tipi di telefoni più comuni dai primi ai giorni nostri. Ad esempio, possono essere rappresentati sotto forma di un diagramma mostrato nella Figura 1. Ora, usando l'astrazione, possiamo evidenziare informazioni generali in questa gerarchia di oggetti: un tipo astratto comune di oggetti - telefono, una caratteristica generale del telefono - l'anno della sua creazione e un'interfaccia comune: tutti i telefoni sono in grado di ricevere e inviare chiamate. Ecco come appare in Java: public abstract class AbstractPhone ( private int year; public AbstractPhone (int year) ( this . year = year; ) public abstract void call (int outputNumber) ; public abstract void ring (int inputNumber) ; ) Sulla base di questa classe astratta, saremo in grado di creare nuovi tipi di telefoni nel programma utilizzando altri principi Java OOP di base, che considereremo di seguito.

Incapsulamento

Usando astrazioni evidenziamo generale per tutti gli oggetti. Tuttavia, ogni modello di telefono è individuale e leggermente diverso dagli altri. Come possiamo tracciare i confini del programma e designare questa individualità? Come possiamo assicurarci che nessuno degli utenti possa accidentalmente o intenzionalmente rompere il nostro telefono o tentare di convertire un modello in un altro? Per il mondo degli oggetti reali la risposta è ovvia: è necessario inserire tutte le parti nel corpo del telefono. Dopotutto, se non lo facciamo e lasciamo fuori tutte le parti interne del telefono e i cavi che le collegano, ci sarà sicuramente uno sperimentatore curioso che vorrà "migliorare" il funzionamento del nostro telefono. Per evitare tale interferenza nella progettazione e nel funzionamento di un oggetto, l'OOP utilizza il principio di incapsulamento - un altro principio base dell'OOP, in cui gli attributi e il comportamento di un oggetto sono combinati in un'unica classe, l'implementazione interna dell'oggetto è nascosta l'utente e viene fornita un'interfaccia aperta per lavorare con l'oggetto. Il compito del programmatore è determinare quali attributi e metodi saranno accessibili pubblicamente e quali sono implementazioni interne dell'oggetto e non dovrebbero essere modificate.

Incapsulamento e controllo degli accessi

Diciamo che durante la produzione, sul retro del telefono vengono incise informazioni a riguardo: l'anno di produzione o il logo dell'azienda produttrice. Queste informazioni caratterizzano in modo abbastanza specifico questo modello: le sue condizioni. Possiamo dire che lo sviluppatore del telefono si è preso cura dell'immutabilità di queste informazioni: difficilmente qualcuno penserà di rimuovere l'incisione. Nel mondo Java, lo stato degli oggetti futuri è descritto in una classe utilizzando i campi e il loro comportamento è descritto utilizzando metodi. La possibilità di modificare stato e comportamento viene effettuata utilizzando i modificatori di accesso a campi e metodi - privato, protetto, pubblico , E predefinito (accesso predefinito). Ad esempio, abbiamo deciso che l'anno di creazione, il nome del produttore del telefono e uno dei metodi appartengono all'implementazione interna della classe e non possono essere modificati da altri oggetti nel programma. Utilizzando il codice, la classe può essere descritta come segue: public class SomePhone ( private int year; private String company; public SomePhone (int year, String company) ( this . year = year; this . company = company; ) private void openConnection ( ) ( / /findComutator //openNewConnection... ) public void call () ( openConnection () ; System. out. println ("Chiamare il numero") ; ) public void ring () ( System. out. println ("Ding -ding") ; ) ) Modificatore privato rende i campi e i metodi di una classe disponibili solo all'interno di quella classe. Ciò significa che puoi accedere privato campi dall'esterno è impossibile, così come non c'è modo di chiamare privato metodi. Nascondere l'accesso al metodo openConnection ci lascia inoltre liberi di modificare l'implementazione interna di questo metodo, poiché è garantito che questo metodo non verrà utilizzato da altri oggetti e non ne interromperà il lavoro. Per lavorare con il nostro oggetto, lasciamo aperti i metodi call e ring utilizzando un modificatore pubblico . Anche fornire metodi pubblici per lavorare con un oggetto fa parte del meccanismo di incapsulamento, poiché se l'accesso a un oggetto viene completamente negato, diventerà inutile.

Eredità

Diamo nuovamente un'occhiata alla tabella telefonica. Puoi vedere che rappresenta una gerarchia in cui il modello situato sotto ha tutte le caratteristiche dei modelli situati più in alto sul ramo, più le proprie. Ad esempio, uno smartphone utilizza una rete cellulare per la comunicazione (ha le proprietà di un telefono cellulare), è wireless e portatile (ha le proprietà di un telefono cordless) e può ricevere ed effettuare chiamate (ha le proprietà di un telefono). In questo caso possiamo parlare di ereditarietà delle proprietà dell'oggetto. Nella programmazione, l'ereditarietà è l'uso di classi esistenti per definirne di nuove. Diamo un'occhiata a un esempio di creazione di una classe smartphone utilizzando l'ereditarietà. Tutti i telefoni cordless sono alimentati da batterie ricaricabili, che hanno una certa autonomia operativa in poche ore. Aggiungiamo quindi questa proprietà alla classe del telefono cordless: public abstract class WirelessPhone extends AbstractPhone ( private int hour; public WirelessPhone (int year, int hour) ( super (year) ; this . hour = hour; ) ) I telefoni cellulari ereditano le proprietà di un telefono cordless, abbiamo anche aggiunto un'implementazione dei metodi di chiamata e squillo a questa classe: public class CellPhone extends WirelessPhone ( public CellPhone (int year, int hour) ( super (year, hour); ) @Override public void call ( int outputNumber) ( System. out. println ("Numero chiamante " + outputNumber) ; ) @Override public void ring (int inputNumber) ( System. out. println ( "Un utente ti sta chiamando"+ numero di ingresso) ; ) ) E infine la classe degli smartphone che, a differenza dei classici cellulari, dispone di un sistema operativo completo. Puoi aggiungere nuovi programmi supportati da questo sistema operativo al tuo smartphone, espandendone così le funzionalità. Utilizzando il codice, la classe può essere descritta come segue: public class Smartphone extends CellPhone ( private String OperationSystem; public Smartphone (int anno, int ora, String OperationSystem) ( super (anno, ora); this . OperationSystem = OperationSystem; ) public void install (String program) ( System. out. println ("I install " + program + "for" + OperationSystem) ; ) ) Come puoi vedere, abbiamo creato pochissimo nuovo codice per descrivere la classe Smartphone, ma abbiamo ottenuto un nuovo classe con nuove funzionalità. L’utilizzo di questo principio di OOP Java può ridurre significativamente la quantità di codice e quindi facilitare il lavoro del programmatore.

Polimorfismo

Se consideriamo tutti i modelli di telefono, nonostante le differenze nell'aspetto e nel design dei modelli, possiamo identificare alcuni comportamenti comuni in essi: tutti possono ricevere ed effettuare chiamate e hanno un set di pulsanti di controllo abbastanza chiaro e semplice. Applicando uno dei principi di base dell'OOP, a noi già noto, l'astrazione in termini di programmazione, possiamo dire che l'oggetto telefono ha un'interfaccia comune. Pertanto, gli utenti del telefono possono utilizzare comodamente diversi modelli utilizzando gli stessi pulsanti di controllo (meccanici o touch), senza entrare nei dettagli tecnici del dispositivo. Quindi usi costantemente un telefono cellulare e puoi facilmente effettuare una chiamata dalla sua controparte fissa. Viene chiamato il principio in OOP secondo cui un programma può utilizzare oggetti con la stessa interfaccia senza informazioni sulla struttura interna dell'oggetto polimorfismo . Immaginiamo che nel nostro programma dobbiamo descrivere un utente che può utilizzare qualsiasi modello di telefono per chiamare un altro utente. Ecco come farlo: public class User ( private String name; public User (String name) ( this . name = name; ) public void callAnotherUser (int number, AbstractPhone phone) ( // questo è polimorfismo: utilizza il tipo astratto AbstractPhone phone nel codice! telefono. chiama il numero) ; ) ) ) Ora descriveremo i vari modelli di telefono. Uno dei primi modelli di telefono: public class ThomasEdisonPhone extends AbstractPhone ( public ThomasEdisonPhone (int year) ( super (year) ; ) @Override public void call (int outputNumber) ( System. out. println ("Ruota la manopola" ) ; System .out .println( "Per favore, fornisca il suo numero di telefono, signore") ; ) @Override public void ring (int inputNumber) ( System. out. println ("Il telefono sta squillando" ) ; ) ) Telefono fisso normale: public class Phone extends AbstractPhone ( public Phone (int anno) ( super (anno) ; ) @Override public void call (int outputNumber) ( System. out. println ("Chiamare il numero" + outputNumber) ; ) @Override public void ring (int inputNumber) ( System. out. println ("Il telefono sta squillando") ; ) ) E infine, un fantastico videotelefono: public class VideoPhone extends AbstractPhone ( public VideoPhone (int year) ( super (year) ; ) @Override public void call (int outputNumber) ( System. out. println ( "Sto collegando un canale video per l'abbonato"+numero di uscita); ) @Override public void ring (int inputNumber) ( System. out. println ( "Hai una videochiamata in arrivo..."+ numero di ingresso) ; ) ) Creiamo oggetti nel metodo main() e testiamo il metodo callAnotherUser: AbstractPhone firstPhone = new ThomasEdisonPhone (1879) ; AbstractPhone telefono = nuovo telefono (1984); AbstractPhone videoPhone= nuovo VideoPhone (2018) ; Utente utente = nuovo Utente ("Andrey"); utente. callAnotherUser(224466, primoTelefono); // Ruota la manopola //Per favore, fornisca il numero dell'abbonato, signore utente. chiamaUnAltroUtente(224466, telefono); //Chiamo il numero 224466 utente. chiamaUnAltroUtente(224466, videotelefono); //Collegamento di un canale video per l'abbonato 224466 Chiamando lo stesso metodo sull'oggetto utente, abbiamo ottenuto risultati diversi. L'implementazione specifica del metodo di chiamata all'interno del metodo callAnotherUser è stata selezionata dinamicamente in base al tipo specifico dell'oggetto chiamante durante l'esecuzione del programma. Questo è il vantaggio principale del polimorfismo: la scelta dell'implementazione durante l'esecuzione del programma. Negli esempi di classi phone precedenti abbiamo utilizzato l'override del metodo, una tecnica che modifica l'implementazione del metodo definita nella classe base senza modificare la firma del metodo. Si tratta essenzialmente di una sostituzione del metodo ed è il nuovo metodo definito nella sottoclasse che viene chiamato quando viene eseguito il programma. In genere, quando si sovrascrive un metodo, viene utilizzata l'annotazione @Override, che indica al compilatore di verificare le firme dei metodi sovrascritti e sovrascritti. Infine Per garantire che lo stile del tuo programma corrisponda al concetto di OOP e ai principi di OOP Java, segui questi suggerimenti:
  • evidenziare le caratteristiche principali dell'oggetto;
  • evidenziare proprietà e comportamenti comuni e utilizzare l'ereditarietà durante la creazione di oggetti;
  • utilizzare tipi astratti per descrivere oggetti;
  • Cerca di nascondere sempre metodi e campi relativi all'implementazione interna della classe.

(come sta per OOP) è, prima di tutto, un paradigma di programmazione.
Paradigma di programmazione definisce come il programmatore vede l'esecuzione del programma.
Pertanto, il paradigma OOP è caratterizzato dal fatto che il programmatore vede il programma come un insieme di oggetti interagenti, mentre, ad esempio, nella programmazione funzionale il programma è rappresentato come una sequenza di calcoli di funzioni. La programmazione procedurale, o, come viene anche correttamente chiamata, programmazione operativa classica, implica la scrittura di un algoritmo per risolvere un problema; in questo caso non vengono descritte né indicate le proprietà attese del risultato finale. La programmazione strutturata segue fondamentalmente gli stessi principi della programmazione procedurale, con l'aggiunta di alcune tecniche utili.
I paradigmi di programmazione non procedurale, che includono il paradigma orientato agli oggetti, hanno idee completamente diverse.
La definizione di Gradi Bucha recita: “ Programmazione orientata agli oggettiè una metodologia di programmazione basata sulla rappresentazione di un programma come una raccolta di oggetti, ognuno dei quali è un'implementazione di una classe specifica (un tipo speciale), e le classi formano una gerarchia basata sui principi di ereditarietà.
La programmazione strutturata e orientata agli oggetti si basa sul metodo scientifico noto come decomposizione- un metodo che utilizza la struttura del problema e consente di suddividere la soluzione di un grande problema comune nella risoluzione di una sequenza di problemi più piccoli. Decomposizione dell'OOP avviene non secondo algoritmi, ma secondo oggetti utilizzati per risolvere il problema. Questa scomposizione riduce la dimensione dei sistemi software riutilizzando meccanismi comuni. È noto che i sistemi di programmazione visiva o i sistemi costruiti sui principi della programmazione orientata agli oggetti sono più flessibili e si evolvono più facilmente nel tempo.

Storia dello sviluppo dell'OOP ha origine alla fine degli anni '60. Il primo linguaggio orientato agli oggetti è stato il linguaggio di programmazione Simula, creato in un centro informatico in Norvegia. Il linguaggio aveva lo scopo di modellare situazioni del mondo reale. Una caratteristica speciale di Simula era che il programma scritto nel linguaggio era organizzato in oggetti di programmazione. Gli oggetti avevano istruzioni chiamate metodi e dati chiamati variabili; metodi e dati determinavano il comportamento dell'oggetto. Durante la simulazione, l'oggetto si è comportato secondo il suo comportamento predefinito e, se necessario, ha modificato i dati per riflettere l'impatto dell'azione assegnatagli.

Oggi ce n'è un numero sufficiente linguaggi di programmazione orientati agli oggetti, i più popolari dei quali sono attualmente C++, Delphi, Java, Visual Basic, Flash. Ma, in più, molti linguaggi che di solito sono classificati come paradigmi procedurali hanno anche proprietà OOP, essendo in grado di lavorare con oggetti. Pertanto, la programmazione orientata agli oggetti in C costituisce un'ampia sezione della programmazione in questo linguaggio, lo stesso vale per l'OOP in Python e in molti altri linguaggi strutturati.

Quando si parla di OOP, spesso emerge un'altra definizione: programmazione visiva. Fornisce inoltre un ampio uso di prototipi di oggetti, definiti come classi di oggetti.
Eventi. Molti ambienti di programmazione visiva implementano una caratteristica (oltre all'incapsulamento, al polimorfismo e all'ereditarietà) di un oggetto: un evento. Gli eventi nella programmazione orientata agli oggetti sono la capacità di elaborare i cosiddetti messaggi (o eventi) ricevuti dal sistema operativo Windows o dal programma stesso. Questo principio è tipico di tutti i componenti dell'ambiente che elaborano vari eventi che si verificano durante l'esecuzione del programma. Essenzialmente, un evento è un'azione che attiva una reazione standard di un oggetto. Un evento può essere considerato, ad esempio, un clic su un pulsante del mouse, il passaggio del cursore del mouse su una voce di menu, l'apertura di una scheda, ecc. L'ordine in cui vengono eseguite determinate azioni è determinato proprio dagli eventi che si verificano nel sistema e dalla reazione degli oggetti ad essi.
Classi e oggetti in OOP- vari concetti. Il concetto di classe in OOP è un tipo di dati (lo stesso di, ad esempio, Real o String) e un oggetto è un'istanza specifica di una classe (la sua copia), archiviata nella memoria del computer come variabile del tipo corrispondente .
Classeè un tipo di dati strutturale. La classe include una descrizione dei campi dati, nonché procedure e funzioni che operano su questi campi dati. Metodo OOP- queste sono tali procedure e funzioni in relazione alle classi.
Le classi hanno campi (come il tipo di dati record), proprietà simili ai campi, ma hanno descrittori aggiuntivi che definiscono i meccanismi per la scrittura e la lettura dei dati e metodi - subroutine che mirano a modificare i campi e le proprietà della classe.

Principi di base dell'OOP

I principi della programmazione orientata agli oggetti, oltre alla gestione degli eventi, sono l'incapsulamento, l'ereditarietà, la sottoclasse e il polimorfismo. Sono particolarmente utili e necessari quando si sviluppano applicazioni replicabili e di facile manutenzione.
Un oggetto combina metodi e proprietà che non possono esistere separatamente da esso. Pertanto, se un oggetto viene eliminato, vengono eliminati anche le sue proprietà e i metodi associati. Durante la copia accade la stessa cosa: l'oggetto viene copiato nel suo insieme. Incapsulamento OOP- questa è la caratteristica descritta.

Principio di ereditarietà OOP e sottoclassi

Assolutamente tutti gli oggetti vengono creati sulla base di classi ed ereditano le proprietà e i metodi di queste classi. A loro volta, le classi possono essere create sulla base di altre classi (genitori), quindi tali classi sono chiamate sottoclassi (discendenti). Le sottoclassi ereditano tutte le proprietà e i metodi della classe genitore. Inoltre, per una sottoclasse o una classe discendente, è possibile definire nuove proprietà e metodi, nonché modificare i metodi della classe genitore. Le modifiche alle proprietà e ai metodi di una classe genitore vengono tracciate nelle sottoclassi create sulla base di questa classe, così come negli oggetti creati sulla base delle sottoclassi. Questo è lo scopo dell'ereditarietà OOP.

Polimorfismo OOP

Nella programmazione orientata agli oggetti, il polimorfismo è caratterizzato dall'intercambiabilità di oggetti con la stessa interfaccia. Questo può essere spiegato in questo modo: una classe figlia eredita istanze dei metodi della classe genitore, ma l'esecuzione di questi metodi può avvenire in modo diverso, corrispondente alle specifiche della classe figlia, cioè modificata.
Cioè, se nella programmazione procedurale il nome di una procedura o funzione identifica in modo univoco il codice eseguito relativo a tale procedura o funzione, allora nella programmazione orientata agli oggetti è possibile utilizzare gli stessi nomi di metodo per eseguire azioni diverse. Cioè, il risultato dell'esecuzione dello stesso metodo dipende dal tipo di oggetto a cui viene applicato il metodo.

Il sito presenta una teoria parziale della programmazione orientata agli oggetti per principianti ed esempi OOP di risoluzione dei problemi. Le lezioni OOP sul sito sono algoritmi dettagliati per il completamento di una determinata attività. Sulla base del completamento di questi lavori di laboratorio, lo studente sarà in grado di risolvere autonomamente altri problemi simili in futuro.
Ti auguriamo un facile e interessante apprendimento della programmazione orientata agli oggetti!

I migliori articoli sull'argomento