Sviluppo Java più intelligente

Uno schema semplice e veloce per velocizzare lo sviluppo di applicazioni Java su larga scala prevede l'uso di interfacce. Le interfacce Java sono un modello per la funzionalità contenuta in un oggetto associato.

Incorporando le interfacce nel tuo prossimo progetto, noterai vantaggi per tutto il ciclo di vita del tuo impegno di sviluppo. La tecnica di codifica su interfacce piuttosto che su oggetti migliorerà l'efficienza del team di sviluppo:

  • Consentire al team di sviluppo di stabilire rapidamente le interazioni tra gli oggetti necessari, senza forzare la definizione iniziale degli oggetti di supporto
  • Consentire agli sviluppatori di concentrarsi sulle loro attività di sviluppo con la consapevolezza che l'integrazione è già stata presa in considerazione
  • Fornire flessibilità in modo che le nuove implementazioni delle interfacce possano essere aggiunte al sistema esistente senza importanti modifiche al codice
  • Applicazione dei contratti concordati dai membri del team di sviluppo per garantire che tutti gli oggetti interagiscano come previsto

Una panoramica

Poiché gli sforzi di sviluppo orientato agli oggetti coinvolgono le interazioni degli oggetti, è essenziale sviluppare e applicare forti contratti tra questi oggetti. La tecnica di codifica per interfacce implica l'utilizzo di interfacce, piuttosto che oggetti, come metodo di comunicazione principale.

Questo articolo introdurrà l'utente al concetto di codifica per interfacce attraverso un semplice esempio. Seguirà un esempio dettagliato, per dimostrare il valore di questo schema in un sistema più ampio che richiede più sviluppatori. Prima di arrivare al codice di esempio, tuttavia, esaminiamo i vantaggi della codifica per le interfacce.

Perché codificare le interfacce?

L'interfaccia Java è un contratto di sviluppo. Assicura che un particolare oggetto soddisfi un dato insieme di metodi. Le interfacce vengono utilizzate in tutta l'API Java per specificare la funzionalità necessaria per l'interazione con gli oggetti. Esempi di utilizzo dell'interfaccia sono i meccanismi di callback ( Event Listeners), patterns ( Observer) e le specifiche ( Runnable, Serializable).

La codifica sulle interfacce è una tecnica mediante la quale gli sviluppatori possono esporre determinati metodi di un oggetto ad altri oggetti nel sistema. Gli sviluppatori che ricevono le implementazioni di queste interfacce hanno la capacità di codificare per l'interfaccia invece di codificare per l'oggetto stesso. In altre parole, gli sviluppatori scriverebbero codice che non interagisce direttamente con un oggetto in quanto tale, ma piuttosto con l'implementazione dell'interfaccia di quell'oggetto.

Un altro motivo per codificare su interfacce piuttosto che su oggetti è che fornisce una maggiore efficienza nelle varie fasi del ciclo di vita di un sistema:

  • Design : i metodi di un oggetto possono essere rapidamente specificati e pubblicati a tutti gli sviluppatori interessati
  • Sviluppo : il compilatore Java garantisce che tutti i metodi dell'interfaccia siano implementati con la firma corretta e che tutte le modifiche all'interfaccia siano immediatamente visibili agli altri sviluppatori
  • Integrazione : c'è la capacità di connettere rapidamente classi o sottosistemi, grazie alle loro interfacce consolidate
  • Test : le interfacce aiutano a isolare i bug perché limitano l'ambito di un possibile errore logico a un dato sottoinsieme di metodi

C'è un certo sovraccarico associato a questa tecnica di sviluppo, a causa dell'infrastruttura di codice richiesta. Questa infrastruttura include entrambe le interfacce per le interazioni tra oggetti e il codice di invocazione per creare implementazioni di interfacce. Questo overhead è insignificante se paragonato alla facilità e ai vantaggi dell'utilizzo delle interfacce descritte.

Esempio di base

Per spiegare ulteriormente il concetto di codifica alle interfacce, ho creato un semplice esempio. Sebbene questo esempio sia chiaramente banale, dimostra alcuni dei vantaggi sopra menzionati.

Considera il semplice esempio di una classe Carche implementa l'interfaccia Vehicle. L'interfaccia Vehicleha un unico metodo chiamato start(). La classe Carimplementerà l'interfaccia fornendo un start()metodo. Altre funzionalità nella Carclasse sono state tralasciate per motivi di chiarezza.

interface Vehicle {// Tutte le implementazioni dei veicoli devono implementare il metodo di avvio public void start (); } class Car implementa Vehicle {// Richiesto per implementare Vehicle public void start () {...}}

Dopo aver gettato le basi Cardell'oggetto, possiamo creare un altro oggetto chiamato Valet. È Valetcompito di avviare il Care portarlo al cliente del ristorante. L' Valetoggetto può essere scritto senza interfacce, come segue:

class Valet {public Car getCar (Car c) {...}} 

L' Valetoggetto ha un metodo chiamato getCarche restituisce un Caroggetto. Questo esempio di codice soddisfa i requisiti funzionali del sistema, ma collega per sempre l' Valetoggetto con quello del Car. In questa situazione, si dice che i due oggetti siano strettamente accoppiati. L' Valetoggetto richiede la conoscenza Cardell'oggetto e ha accesso a tutti i metodi e le variabili pubblici contenuti all'interno di quell'oggetto. È meglio evitare un accoppiamento così stretto del codice perché aumenta le dipendenze e riduce la flessibilità.

Per codificare l' Valetoggetto utilizzando le interfacce, è possibile utilizzare la seguente implementazione:

class Valet {public Vehicle getVehicle (Vehicle c) {...}} 

Sebbene le modifiche al codice siano piuttosto minori - cambiando i riferimenti da Cara Vehicle- gli effetti sul ciclo di sviluppo sono considerevoli. Utilizzando la seconda implementazione, Valetconosce solo i metodi e le variabili definiti Vehiclenell'interfaccia. Eventuali altri metodi e dati pubblici contenuti nella specifica implementazione Vehicledell'interfaccia sono nascosti all'utente Vehicledell'oggetto.

Questa semplice modifica del codice ha assicurato il corretto occultamento delle informazioni e dell'implementazione da altri oggetti, e ha quindi eliminato la possibilità che gli sviluppatori utilizzino metodi indesiderabili.

Creazione dell'oggetto interfaccia

L'ultima questione da discutere rispetto a questa tecnica di sviluppo è la creazione degli oggetti dell'interfaccia. Sebbene sia possibile creare una nuova istanza di una classe utilizzando l' newoperatore, non è possibile creare direttamente un'istanza di un'interfaccia. Per creare un'implementazione dell'interfaccia, è necessario creare un'istanza dell'oggetto e trasmetterlo all'interfaccia desiderata. Pertanto, lo sviluppatore che possiede il codice dell'oggetto può essere responsabile sia della creazione dell'istanza dell'oggetto che dell'esecuzione del casting.

Questo processo di creazione può essere ottenuto utilizzando un Factorymodello in cui un oggetto esterno chiama un createXYZ()metodo statico su a Factorye restituisce un'interfaccia. Può anche essere ottenuto se uno sviluppatore chiama un metodo su un altro oggetto e gli passa un'interfaccia invece della classe effettiva. Questo sarebbe analogo al passaggio di Enumerationun'interfaccia invece di un Vectoro Hashtable.

Esempio dettagliato

Per dimostrare l'uso di questo schema su un progetto più ampio, ho creato l'esempio di un pianificatore di riunioni. Questo scheduler ha tre componenti principali: le risorse (sala conferenze e partecipante alla riunione), l'occorrenza (la riunione stessa) e lo scheduler (colui che gestisce il calendario delle risorse).

Supponiamo che questi tre componenti debbano essere sviluppati da tre diversi sviluppatori. L'obiettivo di ogni sviluppatore dovrebbe essere quello di stabilire l'utilizzo del proprio componente e pubblicarlo agli altri sviluppatori del progetto.

Considera l'esempio di un file Person. A Personpuò implementare numerosi metodi ma implementerà l' Resourceinterfaccia per questa applicazione. Ho creato l' Resourceinterfaccia con tutti i metodi di accesso necessari per tutte le risorse utilizzate in questo esempio (mostrato sotto):

interfaccia pubblica Risorsa {public String getID (); public String getName (); public void addOccurrence (Occurrence o); }

At this point, the developer of the Person functionality has published the interface by which all users can access the information stored in the Person object. Coding to the interface helps ensure that no developers are using the Person object in an incorrect manner. The developer of the Scheduler object can now use the methods contained in the Resource interface to access the information and functionality necessary to create and maintain the schedule of the Person object.

The Occurrence interface contains methods necessary for the scheduling of an Occurrence. This can be a conference, travel plan, or any other scheduling event. The Occurrence interface is shown below:

public interface Occurrence { public void setEndDatetime(Date d); public Date getEndDatetime(); public void setStartDatetime(Date d); public Date getStartDatetime(); public void setDescription(String description); public String getDescription(); public void addResource(Resource r); public Resource[] getResources(); public boolean occursOn( Date d); } 

The Scheduler code uses the Resource interface and the Occurrence interface to maintain the schedule of a resource. Notice that the Scheduler does not have any knowledge of the entity for which it is maintaining the schedule:

public class Scheduler implements Schedule{ Vector schedule = null; public Scheduler(){ schedule = new Vector(); } public void addOccurrence(Occurrence o){ schedule.addElement(o); } public void removeOccurrence(Occurrence o){ schedule.removeElement(o); } public Occurrence getOccurrence(Date d) { Enumeration scheduleElements = schedule.elements(); Occurrence o = null; while ( scheduleElements.hasMoreElements() ) { o = (Occurrence) scheduleElements.nextElement(); // For this simple example, the occurrence matches if // the datetime isthe meeting start time. This logic // can be made more complex as required. if ( o.getStartDatetime() == d) { break; } } return o; } } 

This example shows the power of interfaces in the development phases of a system. Each of the subsystems has knowledge only of the interface through which it must communicate -- no knowledge of the implementation is required. If each of the building blocks in the above example were to be further developed by teams of developers, their efforts would be simplified due to the enforcement of these interface contracts.

Final thoughts on interfaces

This article has demonstrated some of the benefits of coding to interfaces. This technique enables greater efficiency throughout each phase of the development lifecycle.

During the design phases of the project, interfaces allow the quick establishment of the desired interactions among objects. The implementation objects associated with a given interface can be defined after the methods and requirements for that interface are specified. The more quickly the interaction is established, the more quickly the design phase can progress into development.

Interfaces give developers the ability to expose and limit certain methods and information to the users of their objects without changing the permissions and internal structure of the object itself. The use of interfaces can help eliminate the pesky bugs that appear when code developed by multiple development teams is integrated.

Contract enforcement is provided by the interface. Because the interface is generally agreed upon during the design phase of the project, the developers have the ability to concentrate on their individual modules without having to worry about the modules of their colleagues. Integrating these subsystems is made more efficient by the fact that the contracts have already been enforced throughout the development phase.

For testing purposes, a simple driver object can be created to implement the agreed-upon interfaces. Using this object, developers can continue their work with the knowledge that they are using the proper methods to access the object. When the objects are deployed in a test environment, the driver classes are replaced by the true classes, allowing the object to be tested without code or property changes.

This scheme provides the capability for easy expansion of this system; in our example, we could expand the code to include more forms of resources, such as meeting rooms and audio/video equipment. Any additional implementation of the Resource interface will fit into the established mechanism without modifying the existing code. Large-scale projects using this scheme could be designed and implemented in such a way that additional functionality can be added without major modification to the infrastructure. As an example, the ConferenceRoom object was created. This object implements the Resource interface and can interact with the Schedule and Occurrence implementers without changing the infrastructure.

Un altro vantaggio è la posizione centralizzata del codice. Se devono essere aggiunti nuovi metodi Resourceall'interfaccia, tutte le implementazioni di questa interfaccia saranno identificate come che richiedono modifiche. Ciò ridurrà le indagini necessarie per determinare il possibile impatto delle modifiche all'interfaccia.

Oltre ai vantaggi di sviluppo, la tecnica presentata in questo articolo fornisce alla gestione del progetto la garanzia che i modelli di comunicazione tra oggetti o sistemi siano stati stabiliti e applicati durante tutto il ciclo di sviluppo. Ciò riduce il rischio di guasti durante le fasi di integrazione e test del progetto.