Développement Java plus intelligent

Un schéma simple et rapide pour accélérer le développement d'applications Java à grande échelle implique l'utilisation d'interfaces. Les interfaces Java sont un modèle pour les fonctionnalités contenues dans un objet associé.

En incorporant des interfaces dans votre prochain projet, vous remarquerez des avantages tout au long du cycle de vie de votre effort de développement. La technique de codage en interfaces plutôt qu'en objets améliorera l'efficacité de l'équipe de développement en:

  • Permettre à l'équipe de développement d'établir rapidement les interactions entre les objets nécessaires, sans forcer la définition précoce des objets supports
  • Permettre aux développeurs de se concentrer sur leurs tâches de développement en sachant que l'intégration a déjà été prise en compte
  • Fournir de la flexibilité afin que de nouvelles implémentations des interfaces puissent être ajoutées au système existant sans modification majeure du code
  • Faire respecter les contrats convenus par les membres de l'équipe de développement pour s'assurer que tous les objets interagissent comme prévu

Un aperçu

Étant donné que les efforts de développement orienté objet impliquent les interactions d'objets, il est essentiel de développer et d'appliquer des contrats forts entre ces objets. La technique de codage des interfaces implique l'utilisation d'interfaces, plutôt que d'objets, comme principale méthode de communication.

Cet article présente à l'utilisateur le concept de codage des interfaces à travers un exemple simple. Un exemple détaillé suivra, aidant à démontrer la valeur de ce schéma dans un système plus grand nécessitant plusieurs développeurs. Avant d'arriver à l'exemple de code, cependant, examinons les avantages du codage aux interfaces.

Pourquoi coder vers des interfaces?

L'interface Java est un contrat de développement. Il garantit qu'un objet particulier satisfait un ensemble donné de méthodes. Les interfaces sont utilisées dans toute l'API Java pour spécifier les fonctionnalités nécessaires à l'interaction avec les objets. Des exemples d'utilisation d'interface sont les mécanismes de rappel ( Event Listeners), les modèles ( Observer) et les spécifications ( Runnable, Serializable).

Le codage aux interfaces est une technique par laquelle les développeurs peuvent exposer certaines méthodes d'un objet à d'autres objets du système. Les développeurs qui reçoivent des implémentations de ces interfaces ont la possibilité de coder sur l'interface au lieu de coder sur l'objet lui-même. En d'autres termes, les développeurs écriraient du code qui n'interagissait pas directement avec un objet en tant que tel, mais plutôt avec l'implémentation de l'interface de cet objet.

Une autre raison de coder sur des interfaces plutôt que sur des objets est qu'il offre une efficacité plus élevée dans les différentes phases du cycle de vie d'un système:

  • Conception : les méthodes d'un objet peuvent être rapidement spécifiées et publiées à tous les développeurs concernés
  • Développement : le compilateur Java garantit que toutes les méthodes de l'interface sont implémentées avec la signature correcte et que toutes les modifications apportées à l'interface sont immédiatement visibles pour les autres développeurs
  • Intégration : il est possible de connecter rapidement des classes ou des sous-systèmes entre eux, grâce à leurs interfaces bien établies
  • Test : les interfaces permettent d'isoler les bogues car elles limitent la portée d'une éventuelle erreur logique à un sous-ensemble donné de méthodes

Il y a une certaine surcharge associée à cette technique de développement, en raison de l'infrastructure de code requise. Cette infrastructure comprend à la fois des interfaces pour les interactions entre objets et du code d'appel pour créer des implémentations d'interfaces. Cette surcharge est insignifiante par rapport à la facilité et l'avantage d'utiliser les interfaces comme décrit.

Exemple de base

Pour expliquer davantage le concept de codage aux interfaces, j'ai créé un exemple simple. Bien que cet exemple soit clairement trivial, il démontre certains des avantages mentionnés ci-dessus.

Prenons l'exemple simple d'une classe Carqui implémente une interface Vehicle. L'interface Vehiclea une seule méthode appelée start(). La classe Carimplémentera l'interface en fournissant une start()méthode. D'autres fonctionnalités de la Carclasse ont été laissées de côté pour des raisons de clarté.

interface Vehicle {// Toutes les implémentations de véhicules doivent implémenter la méthode de démarrage public void start (); } class Car implements Vehicle {// Requis pour implémenter Vehicle public void start () {...}}

Après avoir jeté les bases de l' Carobjet, nous pouvons créer un autre objet appelé Valet. C'est le Valettravail de démarrer le Caret de l'apporter au client du restaurant. L' Valetobjet peut être écrit sans interface, comme suit:

valet de classe {voiture publique getCar (voiture c) {...}} 

L' Valetobjet a une méthode appelée getCarqui renvoie un Carobjet. Cet exemple de code satisfait les exigences fonctionnelles du système, mais il lie pour toujours l' Valetobjet à celui du Car. Dans cette situation, on dit que les deux objets sont étroitement couplés. L' Valetobjet nécessite la connaissance de l' Carobjet et a accès à toutes les méthodes et variables publiques contenues dans cet objet. Il est préférable d'éviter un couplage aussi étroit du code car cela augmente les dépendances et réduit la flexibilité.

Pour coder l' Valetobjet à l'aide d'interfaces, l'implémentation suivante peut être utilisée:

valet de classe {véhicule public getVehicle (véhicule c) {...}} 

Bien que les changements de code soient assez mineurs - changer les références de Carà Vehicle- les effets sur le cycle de développement sont considérables. En utilisant la deuxième implémentation, le Valetn'a connaissance que des méthodes et des variables définies dans l' Vehicleinterface. Toutes les autres méthodes et données publiques contenues dans l'implémentation spécifique de l' Vehicleinterface sont cachées à l'utilisateur de l' Vehicleobjet.

Ce simple changement de code a assuré la dissimulation appropriée des informations et de l'implémentation des autres objets, et a donc éliminé la possibilité que les développeurs utilisent des méthodes indésirables.

Création de l'objet d'interface

Le dernier problème à discuter par rapport à cette technique de développement est la création des objets d'interface. Bien qu'il soit possible de créer une nouvelle instance d'une classe à l'aide de l' newopérateur, il n'est pas possible de créer directement une instance d'une interface. Afin de créer une implémentation d'interface, vous devez instancier l'objet et le transtyper dans l'interface souhaitée. Par conséquent, le développeur qui possède le code objet peut être responsable à la fois de la création de l'instance de l'objet et de l'exécution du cast.

Ce processus de création peut être réalisé en utilisant un Factorymodèle dans lequel un objet externe appelle une createXYZ()méthode statique sur a Factoryet renvoie une interface. Cela peut également être réalisé si un développeur appelle une méthode sur un autre objet et lui transmet une interface au lieu de la classe réelle. Ce serait analogue au passage d'une Enumerationinterface au lieu d'un Vectorou Hashtable.

Exemple détaillé

Afin de démontrer l'utilisation de ce schéma sur un projet plus vaste, j'ai créé l'exemple d'un planificateur de réunion. Ce planificateur comporte trois composants principaux: les ressources (salle de conférence et participant à la réunion), l'occurrence (la réunion elle-même) et le planificateur (celui qui gère le calendrier des ressources).

Let's assume these three components were to be developed by three different developers. The goal of each developer should be to establish the usage of his or her component and publish it to the other developers on the project.

Consider the example of a Person. A Person may implement numerous methods but will implement the Resource interface for this application. I have created the Resource interface with all the necessary accessor methods for all resources used in this example (shown below):

public interface Resource { 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 autre avantage est l'emplacement centralisé du code. Si de nouvelles méthodes doivent être ajoutées à l' Resourceinterface, toutes les implémentations de cette interface seront identifiées comme nécessitant des modifications. Cela réduira les recherches nécessaires pour déterminer l'impact possible des modifications apportées à l'interface.

Outre les avantages du développement, la technique présentée dans cet article fournit à la gestion de projet l'assurance que des modèles de communication interobjets ou intersystèmes ont été établis et appliqués tout au long du cycle de développement. Cela réduit le risque d'échecs lors des phases d'intégration et de test du projet.