MokaByte
Numero 34 - Ottobre 99
|
||||
|
|
|||
|
|
|
||
Le interfacce sono uno strumento molto importante per il design e l'implementazione di sistemi OO. In questo articolo approfondiremo le interfacce in Java e il loro utilizzo per migliorare la riusabilità e l'estendibilità del software |
Introduzione
public interface Runnable {
public class Test {
Utilizzi delle interfacceIn questo paragrafo vedremo alcuni utilizzi tipici delle interfacce in Java e ne analizzeremo i vantaggi a livello di design e di implementazione. Inoltre considereremo gli aspetti di ereditarietà fra interfacce e di creazione di oggetti dei quali è nota solo l'interfaccia.Interfacce e polimorfismoIl polimorfismo ottenuto attraverso l'ereditarietà è uno strumento molto potente. Le interfacce ci permettono di sfruttare il polimorfismo anche senza ricorrere a gerarchie di ereditarietà. Vediamo un esempiopublic interface PricedItem {
public
class Book implements PricedItem {
public double getPrice() {
Il codice client che necessita del prezzo di un libro può quindi essere scritto così:
double computeTotalPrice(Collection items)
{
while (it.hasNext()) {
Il metodo precedente calcola il prezzo totale di una collezione di oggetti. Supponiamo ora di voler estendere l'applicazione per gestire non solo libri ma anche CD musicali. Introdurremo a questo proposito una nuova classe che implementa l'interfaccia PricedItem
public class CD implements PricedItem
{
public void setPrice(double price) {
public double getPrice() {
Il codice precedente
per il calcolo del prezzo totale funziona senza modifiche anche se la collezione
contiene oggetti di classe CD perchè tale codice fà riferimento
all'interfaccia e non alla classe concreta.
"Ereditarietà" multiplaCome noto Java non supporta l'ereditarietà multipla fra classi. Una classe può però implementare più interfacce e in questo modo possiamo per essa definire diversi comportamenti. Riprendiamo ora l'esempio precedente e aggiungiamo alle nostre classi il supporto alla persistenza. L'interfaccia Persistent fà al caso nostro
public interface Persistent {
Le nostre classi diventano quindi
public class Book implements PricedItem, Persistable {
public class CD implements PricedItem, Persistable {
Quindi possiamo scrivere il codice di gestione del salvataggio nel seguente modo
public
void saveAll(Collection items) {
while (it.hasNext()) {
Osserviamo che l'interfaccia Persistent nasconde completamente i dettagli di salvataggio che potrebbe avvenire su file oppure su DB attraverso JDBC.
ComposizioneLa programmazione OO permette di riutilizzare funzionalità esistenti principalmente attraverso ereditarietà fra classi e composizione di oggetti. La composizione permette di ottenere sistemi più flessibili mentre l'ereditarietà dovrebbe essere utilizzata principalmente per modellare relazioni costanti nel tempo [4]. Non dobbiamo però pensare di poter ottenere il polimorfismo solo con l'ereditarietà: l'utilizzo combinato di interfacce e composizione ci permette di progettare soluzioni molto interessanti dal punto di vista architetturale. Vediamo un esempio. Supponiamo di dover sviluppare il supporto alla validazione per le classi Book viste prima. Le logiche di validazione saranno incorporate all'interno di un'opportuna classe che implementa una interfaccia Validator
public interface Validator {
public class BookValidator implements Validator
{
Vediamo ora la classe che si occupa di eseguire la validazione
public class Manager {
public Manager(Validator validator) {
public void validate() {
La classe
Manager non fà riferimento alla classe concreta BookValidator quindi
possiamo cambiare la logica di validazione anche a run-time. La soluzione
di design che abbiamo visto è nota come pattern Stategy [5].
Interfacce che estendono altre interfacceCome le classi anche le interfacce possono essere organizzate in gerarchie di ereditarietà. Ad esempio
interface Base {
L'interfaccia Extended eredita quindi tutte le costanti e tutti i metodi dichiarati in Base. Ogni classe che implementa Extended dovrà quindi fornire una definizione anche per i metodi dichiarati in Base. Le interfacce possono poi derivare da più interfacce, allo stesso modo in cui una classe può implementare più interfacce.
interface Sibling { ...}
Vediamo ora come vengono gestiti i conflitti di nomi. Se due interfacce contengono due metodi con la stessa signature e con lo stesso valore di ritorno allora la classe concreta dovrà implementare il metodo solo una volta e il compilatore non segnalerà alcun errore. Se i metodi hanno invece lo stesso nome ma signature diverse allora la classe concreta dovrà dare un'implementazione per entrambi i metodi. I problemi si verificano quando le interfacce dichiarano due metodi con gli stessi parametri ma diverso valore di ritorno. Es.
interface Int1 {
interface Int2 {
In questo caso
il compilatore segnala un errore perchè il linguaggio non permette
che una classe abbia due metodi la cui signature differisce solo per il
tipo del valore di ritorno. Consideriamo infine il caso in cui due interfacce
dichiarino due costanti con lo stesso nome, eventualmente anche con tipo
diverso. La classe concreta potrà utilizzare entrambe le costanti
qualificandole con il nome dell'interfaccia.
Interfacce e creazione di oggettiCome abbiamo visto le interfacce permettono di astrarre dai dettagli implementativi, eliminare le dipendenze da classi concrete e porre l'attenzione sul ruolo degli oggetti nell'architettura che si vuole sviluppare. Rimane però un problema relativo alla creazione degli oggetti: in tale situazione occorre comunque specificare il nome di una classe concreta. In questo caso si genera quindi una dipendenza di creazione [3]. Ad esempio
public class MyDocument {
MyDocument doc = new MyDocument(); Nell'istruzione precedente si crea un oggetto di classe concreta MyDocument ma il codice non potrà essere riutilizzato per creare un oggetto di classe estesa da MyDocument oppure un'altra classe che rappresenta un diverso tipo di documento. Come osservato sempre in [3] è possibile risolvere questo problema creando classi final oppure rendendo ridefinibile l'operazione di creazione. Quest'ultima soluzione è senz'altro la più interessante e i pattern creazionali [5] ci permettono di risolvere il problema. Vediamo ora come applicare il pattern Abstract Factory per incapsulare il processo di creazione ed eliminare la dipendenze di creazione di cui soffriva il precedente esempio. Innanzi tutto introduciamo un'interfaccia per rappresentare un documento
public interface Document {
A questo punto definiamo un'interfaccia per un factory, cioè un oggetto il cui compito è quello di creare altri oggetti. Poichè a priori non sappiamo quale tipo di oggetti dovranno essere creati ricorriamo alle interfacce
public interface DocumentFacory
{
Possiamo ora definire diversi tipi di documento, ad esempio
public class TechicalDocument implements Document {
public class CommercialDocument
implements Document {
Ora vogliamo essere in grado di creare diversi tipi di documento. Per questo definiamo una classe factory per ogni diversa classe documento
public class TechicalDocumentFactory
implements DocumentFactory {
public class CommercialDocumentFactory implements DocumentFactory {
Possiamo quindi creare oggetti documento nel modo seguente
void manageDocument(DocumentFactory
factory) {
Il codice precedente
crea un oggetto che implementa l'interfaccia Document ma non ha alcun legame
con classi concrete e si può quindi utilizzare con classi diverse,
purché conformi all'interfaccia Document.
Vantaggi delle interfacce nello sviluppo del softwareDopo aver passato in rassegna diversi esempi sull'utilizzo delle interfacce possiamo a questo punto discutere sui loro reali vantaggi:
Conclusioni
Riferimenti1.Doug Lea, "Implementing Basic Design Pattern In Java", disponibile come supplemento online di "ConcurrentProgramming in Java" all'URL http://gee.cs.oswego.edu/dl/cpj/ifc.html; 2.Carlo Pescio, "Oggetti ed Interfacce", Computer Programming N. 63, Novembre 1997; 3.Carlo Pescio, "Systematic Object Oriented Design", Computer Programming N. 81, Giugno 1999; 4.Carlo Pescio, "Ereditarietà nei progetti reali", Computer Programming N. 71, Luglio/Agosto 1998; 5.GoF, "Design Patterns", Addison Wesley, 1994; 6.Bill Venners, "Designing with interfaces", JavaWorld, December 1998, http://www.javaworld.com/javaworld/jw-12-1998/jw-12-techniques.html 7.Michael Cymerman, "Smarter Java Development", JavaWorld, August 1999, http://www.javaworld.com/jw-08-1999/jw-08-interfaces.html |
|
||
|
||
MokaByte ricerca
nuovi collaboratori
|
||
|