Java: Fondamenti di programmazione ad oggetti

java-logo

Fondamenti di programmazione ad oggetti

Programmi semplici e non orientati agli oggetti possono consistere di una lunga lista d’ istruzioni.Programmi più complessi raggruppano spesso piccoli blocchi di questi ordini in funzioni o metodi, ciascuna delle quali può realizzare un obiettivo particolare. L’approccio della programmazione orientata agli oggetti, OOP, fa sì che il programmatore localizzi i dati dove non siano direttamente accessibili dal resto del programma. Ai dati stessi si può accedere chiamando (invocando) delle funzioni scritte in modo particolare, comunemente definite metodi, le quali, o sono “impacchettate” con i dati oppure “ereditate” da “oggetti classe.” Tali funzioni agiscono come intermediari per ricuperare o modificare i dati che esse controllano. L’unità di programmazione, nella quale i dati sono associati ad una serie di metodi per gestirli, viene definita in OOP un oggetto. L’architettura software di un programma informatico è l’insieme di dati strutturali necessari per ragionare su di esso: tali dati comprendo- no elementi del software, le relazioni che intercorrono tra di essi e le proprietà degli uni e delle altre. Per definizione l’architettura software è l’insieme di regole pratiche, criteri e schemi di progettazione che regolano:

1)la suddivisione del problema, e del sistema informatico che si deve costruire per risolverlo, in moduli discreti;

2)le tecniche per creare “interfacce” fra tali moduli;

3)le tecniche per gestire la struttura generale ed il flusso del programma;

4)le tecniche per interfacciare il sistema all’ambiente (cioè al linguaggio di programmazione);

Obiect Oriented Programming

L’OOP è una filosofia di progettazione che utilizza linguaggi di programmazione diversi da quelli impiegati dai vecchi linguaggi procedurali (come il Pascal ed il C). È un tipo di programmazione in cui i programmatori non si limitano a definire il tipo di dati in una struttura dati ma definiscono anche i tipi di operazioni (funzioni) che possono essere applicate ad essa. Così la struttura dati diventa un “oggetto” che comprende sia dati che funzioni. Uno dei principali vantaggi delle tecniche OOP, rispetto alle tecniche procedurali, consiste nel fatto che esse mettono in grado i programmatori di creare moduli autosostenibili che non devono essere modificati se si aggiunge un nuovo tipo di oggetto, rendendo il programma più facile da modificare (meno rigido). Si può immaginare che gli oggetti siano una serie di dati “incartati” in una serie di funzioni progettate per far sì che tali dati siano usati in modo appropriato e sia reso più facile il loro impiego.
Un oggetto può inoltre essere in grado di mettere a disposizione semplici metodi standardizzati per eseguire determinate operazioni sui suoi dati e ciò senza rendere accessibile al resto del programma il modo in cui tali operazioni vengono realizzate. Ciò consente di modificare la struttura interna o i metodi di un oggetto senza la necessità di modificare il resto del programma.
Tale caratteristica diventa particolarmente utile se il codice di un programma è scritto da più di un programmatore oppure se l’obiettivo è il riutilizzo di codice tra due programmi diversi. L’OOP è una tecnologia basata non tanto sulle funzioni quanto sui dati con programmi costituiti da moduli autosufficienti (classi), ciascuna istanza delle quali (oggetto classe) contiene tutte le informazioni necessarie per manipolare la sua struttura dati (membri).
Un programma orientato agli oggetti si può considerare una collezione di oggetti interattivi ciascuno dei quali è in grado di processare dati e di scambiare messaggi con gli altri.Tre dei concetti basilari, nella programmazione orientata agli oggetti, sono le Classi, gli Oggetti ed i Metodi ma sono notevolmente importanti anche concetti quali quelli di Astrazione, Incapsulazione, Ereditarietà e Polimorfismo.

Oggetti

Un oggetto è un’ “entità”sulla quale si possono eseguire, un’insieme di attività ad essa correlate: tali attività definiscono il suo comportamento. Gli oggetti sono le entità fondamentali che compaiono in un sistema orientato agli oggetti al momento dell’esecuzione del programma (run time): in tale momento essi inter- agiscono tra di loro scambiandosi messaggi e ciò anche senza essere a conoscenza dei dettagli del loro codice o dei loro dati. Gli oggetti sono caratterizzati da tre proprietà:

1)identità: le caratteristiche di un oggetto che permettono di distinguerlo da altri;

2)stato: descrizione dei dati (proprietà) immagazzinati nell’oggetto;

3)comportamento: descrizione dei metodi associati all’oggetto che consentono il suo impiego.

Così, ad esempio, l’oggetto motocicletta avrà le proprietà marca, mo- dello, capacità del serbatoio, tipo di freni, modello di cambio… ed i metodi accelerare, frenare, cambiare marcia, rifornirsi di benzina…. Formalmente in OOP un oggetto è definito come una delle “istanze” di una “classe” la quale, prima di poter essere usata nel software, de- ve essere istanziata in un oggetto. In ogni momento possono esistere molteplici istanze di una data classe.

Classi

Una classe è la rappresentazione generale di una certa tipologia di oggetti che definisce le variabili ed i metodi comuni a tutti gli oggetti di una certa specie, utilizzato per ottenere, al momento di esecuzione del programma, delle realizzazioni specifiche della classe stessa (i- stanze della classe, oggetti della classe o oggetti semplicemente). Una classe può essere rappresen- tata schematicamente come segue:

Schermata 2013-12-22 alle 15.00.55

public class Teacher

{  // codice

}Teacher objectTeacher = new Teacher();

L’oggetto della classe Teacher, denominato objectTeacher, è stato “i- stanziato” dalla classe Teacher (instanziazione della classe).

Incapsulamento

L’incapsulamento è una metodica che associa codice e dati in un og- getto e li mantiene entrambi al sicuro da interferenze esterne. In OOP l’incapsulamento è realizzato principalmente tramite la creazione di classi le quali rendono accessibili al resto del programma (altre classi) metodi e proprietà ma non i dati che risultano accessibili solo alle funzioni immagazzinate nella stessa classe. La classe è una specie di contenitore, he incapsula la serie di metodi, attributi e proprietà da fornire, a richiesta, alle altre classi. In questo modo l’incapsulamento permette che una classe pos- sa modificare la sua realizzazione interna senza interferire con il funzionamento globale del sistema.Il concetto base dell’incapsulamento è di tener segregate le modalità di funzionamento di una classe ma di permettere l’uso delle sue funzionalità.Nel contesto dell’OOP esistono varie tecniche che consentono a classi diverse di correlarsi: associazione, aggregazione e composizione.

Associazione, aggregazione e composizione.

In OOP la composizione di oggetti è un modo per combinare oggetti semplici in oggetti più complessi. Un esempio di composizione, nel mondo reale, è la relazione tra un’automobile e le sue parti: l’automobile has-a” o “è composta da” oggetti quali il volante, i sedili, la scatola del cambio ed il motore. Se, come nei liguaggi dell’OOP, gli oggetti sono tipizzati allora pos- sono essere compositi o meno, e la composizione può essere considerata una relazione tra tipi: un oggetto di tipo composito (e.g. auto- mobile) “has a” un oggetto di tipo più semplice (e..g. ruota). La sottotipizzazione non descrive una relazione tra oggetti diversi ma afferma che oggetti di un certo tipo sono anche oggetti di un tipo più generale.

// composizione

class Car

{private:
Carburetor* carb;

public:

Car() : carb(new Carburetor()) { }

virtual ~Car() { delete carb; }

};

//Aggregazione

class Pond

{private:

std::vector<Duck*> ducks;

};

L’aggregazione differisce dalla composizione in quanto non implica il possesso. Nella composizione se l’oggetto “possessore” viene di- strutto quelli posseduti subiscono la stessa sorte.

In OOP un’ associazione definisce una relazione tra classi di oggetti che permette ad un oggetto istanza di far sì che un altro compia un’azione in sua vece.In sintesi si può affermare che la aggregazione è un tipo particolare di associazione e che la composizione è un tipo particolare di aggregazione.

Astrazione e generalizzazione

L’astrazione consiste nel fatto che essa consente di omettere i dettagli irrilevanti e di utilizzare nomi per gli oggetti di riferimento. Essa mette l’accento su ciò che un oggetto è o fa piuttosto di come è rappresentato o di come funziona, e questo approccio è fondamentale per controllare la complessità di grandi programmi.Mentre l’astrazione diminuisce la complessità eliminando i dettagli irrilevanti, la generalizzazione ottiene lo stesso risultato rimpiazzan- do una serie di entità che realizzano funzioni simili con un unico costrutto informatico. Le entità astratte vengono generalizate tramite parametrizzazione per aumentarne l’utilità.

Classi astratte

Le classi astratte, dichiarate con la parola chiave abstract, non pos- sono essere istanziate.
Possono essere utilizzate solamente come superclassi per altre classi che estendono la classe astratta.La classe astratta è un concetto la cui realizzazione si completa nel momento in cui esso viene realizzato da una sottoclasse.Oltre a ciò una classe può ereditare solo da una classe astratta.

Interfacce

Ogni classe realizza un’interfaccia fornendo una struttura ed un codice che specifica il funzionamento dei metodi.Un esempio nel mondo reale di un’interfaccia può essere il seguente:

I bottoni di un apparecchio televisivo sono l’interfaccia tra l’utente e i componenti elettrici ed elettronici contenuti nello chassis. Si spinge il bottone “power” per accendere o spegnere l’apparecchio.
Nell’esempio il televisore è l’oggetto ed ogni metodo corrisponde ad uno dei bottoni che, tutti insieme, rappresentano l’interfaccia.
Nella definizione più comune un’interfaccia è la specificazione di un gruppo di metodi correlati indipendentemente dalla realizzazione dei metodi stessi.

Una classe è la descrizione completa di un televisore, che comprende sia i suoi attributi (struttura), che i bottoni (interfaccia).In sintesi l’interfaccia definisce la struttura isolandone la realizzazione, concetto è molto utile qualora sia necessario che l’implementazione (realizzazione) sia intercambiabile.
Oltre a ciò un’interfaccia è molto utile nel caso che l’implementazione cambi frequentemente.
Un’interfaccia può essere impiegata per definire un modello generico e una o più classi astratte che definiscano le realizzazioni parziali dell’interfaccia.Metodi e proprietà astratti sono definiti ma non implementati e se sono pubblici sono accessibili (visibili) da qualsiasi blocco del programma. Un interfaccia di una classe astratta non può essere istanziata.
Nel caso che una classe che realizza un’interfaccia non definisca tutti i metodi dell’interfaccia stessa allora la classe deve essere dichiarata astratta e le definizioni dei metodi devono essere fornite dalla sotto- classe che estende la classe astratta.

Differenze tra una classe ed un’interfaccia

Quando una classe implementa un’interfaccia, un oggetto di tale classe può essere incapsulato all’interno di un’interfaccia.

Se MyLogger è una classe, che implementa ILogger, allora si può scrivere:

ILogger log = new MyLogger();

Concettualmente una classe ed un’interfaccia sono due tipi diversi: mentre una classe pone l’accento sul concetto di incapsulamento, un interfaccia accentua quello di astrazione eliminando i dettagli della implementazione. È molto utile osservare analogie e diffe- renze tra un’interfaccia ed una classe astratta tra le quali, anche se sembrano simili c’è una notevole differenza:

Schermata 2013-12-22 alle 15.28.46

Ereditarietà

È il processo grazie al quale un oggetto acquista le proprietà di un altro oggetto, il che è alla base della classificazione gerarchica.
Senza l’impiego delle gerarchie ogni oggetto richiederebbe una dichiarazione esplicita di tutte le sue proprietà. Grazie all’ereditarietà, invece, è sufficiente definire solo quelle caratteristiche dell’oggetto che lo rendono unico all’interno della sua classe. L’oggetto (sottoclasse) può ereditare tutti i suoi attributi generali dal- le classi gerarchicamente superiori da cui deriva.
La creazione di una classe nuova per estensione di una classe già esistente viene definita ereditarietà.

public class Exception{

//codice

}

public class IOException extends Exceptiom{

//codice

}

In accordo con l’esempio di cui sopra la nuova classe (IOException), che è una classe derivata o sottoclasse, “eredita” i “membri” di una classe già esistente (Exception), che è una super classe o classe di ba- se. La classe IOException può estendere la funzionalità della classe Exception aggiungendo tipi e metodi nuovi e sovra scrivendo quelli preesistenti. Nello stesso modo in cui l’astrazione è strettamente cor- relata alla generalizzazione l’ereditarietà è strettamente correlata alla specializzazione.

Poliformismo

In OOP Poliformismo intende la capacità che le stesse operazioni siano realizzate da un’ampia gamma di differenti tipi di entità.Un operazione può mostrare comportamenti diversi in diverse istanze a seconda dei tipi di dati utilizzati nell’operazione,In OOP il polimorfismo si ottiene grazie a molte differenti tecniche denominate overloading (sovraccarico) dei metodi, overloading degli operatori ed overriding (sovrascrittura) dei metodi.

Overloading dei metodi

L’overloading dei metodi consiste nella possibilità di definire diver- si metodi con lo stesso nome.Per esempio, una funzione radice quadrata potrebbe essere definita in modo da poter operare su numeri reali, numeri complessi o su matrici. L’algoritmo da usare in ciascun caso è diverso ed il risultato restituito può non essere lo stesso.Scrivendo tre funzioni separate con lo stesso nome il programmatore ha il vantaggio di non dover ricordare nomi diversi a seconda del tipo di dati.Nella programmazione orientata agli oggetti qualora una serie di fun- zioni, metodi, con lo stesso nome possa accettare parametri di tipo di- verso si dice che ciascuna delle funzioni è sovraccaricata, overloaded. Per esempio:

public class MyLogger

{
public void LogError(Exception e)

{

// Implementazione }

public bool LogError(Exception e, string message)

{

// Implementazione }

}

Overloading degli operatori

L’overloading degli operatori è un caso particolare di polymorphism in cui uno o più di una serie di operatori come +, – o == sono trattati come funzioni polimorfiche e, come tali, hanno comportamenti diversi a seconda del diverso tipo dei loro argomenti.

public class Complex

{
private int real; public int Real
{ get { return real; } }

private int imaginary; public int Imaginary
{ get { return imaginary; } }

public Complex(int real, int imaginary)

{
this.real = real;

this.imaginary = imaginary; }

public static Complex operator +(Complex c1, Complex c2) {

return new Complex (c1.Real + c2.Real, c1.Imaginary + c2.Imaginary);

}}

Nell’esempio di cui sopra è stato sovraccaricato l’operatore + per sommare due numeri complessi. Le due proprietà denominate Real ed Imaginary sono state dichiarate rendendo accessibile solo il metodo “get”, mentre il costruttore (constructor) dell’oggetto richiede valori obbligatori reali ed immaginari dal costruttore dela classe.

Per esempio:

Complex num1 = new Complex(5, 7); Complex num2 = new Complex(3, 8);

Complex sum = num1 + num2;

Overriding dei metodi

L’overriding di un metodo, in OOP, è una caratteristica del linguaggio che permette ad una sottoclasse di fornire una realizzazione specifica di un metodo che è già fornito da una delle sue superclassi).La realizzazione, nella sottoclasse, rimpiazza quella della superclasse fornendo un metodo che ha lo stesso nome, gli stessi parametri e lo stesso tipo di risposta di quello della superclasse. La versione del metodo che sarà eseguita sarà determinata dall’oggetto che è usato per “invocarla”. Se viene usato un oggetto della superclasse verrà eseguita la versione del metodo della superclasse stessa ma se viene usato un oggetto della sottoclasse allora verrà eseguita la versione della sottoclasse

public class Complex {

private int real; public int Real

{ get { return real; } }

private int imaginary; public int Imaginary
{ get { return imaginary; } }

public Complex(int real, int imaginary)

{
this.real = real;

{
return new Complex(c1.Real+ c2.Real, c1.Imaginary + c2.Imaginary);

}

public override string ToString()

{
return (String.Format(“{0} + {1}i”, real, imaginary));

} }

this.imaginary = imaginary; }

public static Complex operator +(Complex c1, Complex c2)

Nell’esempio di cui sopra è stata estesa l’implementazione dell’operatore + nella classe Complex . Questa classe possiede un metodo sovrascritto denominato “ToString”, che sovrascrive l’implementa- zione per default del metodo standard “ToString” per supportare la corretta conversione in stringa di un numero complesso.

Complex num1 = new Complex(5, 7); Complex num2 = new Complex(3, 8);

// Somma due numeri complessi utilizzando l’operatore + sovraccaricato

Complex sum = num1 + num2;

// Stampa i numeri e la loro somma utilizzando il metodo ToString sovrascritto

Console.WriteLine(“({0}) + ({1}) = {2}”, num1, num2, sum); Console.ReadLine();