Intelligentere Java-Entwicklung

Ein schnelles und einfaches Schema zur Beschleunigung der Entwicklung großer Java-Anwendungen umfasst die Verwendung von Schnittstellen. Java-Schnittstellen sind eine Blaupause für die Funktionalität eines zugeordneten Objekts.

Wenn Sie Schnittstellen in Ihr nächstes Projekt integrieren, werden Sie während des gesamten Lebenszyklus Ihrer Entwicklungsanstrengungen Vorteile feststellen. Die Technik der Codierung auf Schnittstellen anstatt auf Objekte verbessert die Effizienz des Entwicklungsteams durch:

  • Ermöglichen, dass das Entwicklungsteam schnell die Interaktionen zwischen den erforderlichen Objekten herstellt, ohne die frühzeitige Definition der unterstützenden Objekte zu erzwingen
  • Entwickler können sich auf ihre Entwicklungsaufgaben konzentrieren, mit dem Wissen, dass die Integration bereits berücksichtigt wurde
  • Bereitstellung von Flexibilität, damit neue Implementierungen der Schnittstellen ohne größere Codeänderungen zum vorhandenen System hinzugefügt werden können
  • Durchsetzung der von Mitgliedern des Entwicklungsteams vereinbarten Verträge, um sicherzustellen, dass alle Objekte wie geplant interagieren

Ein Überblick

Da objektorientierte Entwicklungsbemühungen die Interaktion von Objekten beinhalten, ist es wichtig, starke Verträge zwischen diesen Objekten zu entwickeln und durchzusetzen. Die Technik der Codierung in Schnittstellen umfasst die Verwendung von Schnittstellen anstelle von Objekten als primäre Kommunikationsmethode.

Dieser Artikel führt den Benutzer anhand eines einfachen Beispiels in das Konzept der Codierung von Schnittstellen ein. Es folgt ein detailliertes Beispiel, das den Wert dieses Schemas in einem größeren System demonstriert, für das mehrere Entwickler erforderlich sind. Bevor wir jedoch zum Beispielcode kommen, wollen wir uns die Vorteile der Codierung für Schnittstellen ansehen.

Warum Code für Schnittstellen?

Die Java-Schnittstelle ist ein Entwicklungsvertrag. Es stellt sicher, dass ein bestimmtes Objekt einen bestimmten Satz von Methoden erfüllt. In der gesamten Java-API werden Schnittstellen verwendet, um die erforderlichen Funktionen für die Objektinteraktion anzugeben. Beispiele für die Verwendung von Schnittstellen sind Rückrufmechanismen ( Event Listeners), Muster ( Observer) und Spezifikationen ( Runnable, Serializable).

Das Codieren an Schnittstellen ist eine Technik, mit der Entwickler bestimmte Methoden eines Objekts anderen Objekten im System aussetzen können. Die Entwickler, die Implementierungen dieser Schnittstellen erhalten, können anstelle der Codierung des Objekts selbst auf der Schnittstelle codieren. Mit anderen Worten, die Entwickler würden Code schreiben, der nicht direkt mit einem Objekt als solchem ​​interagiert, sondern mit der Implementierung der Schnittstelle dieses Objekts.

Ein weiterer Grund für die Codierung an Schnittstellen und nicht an Objekten besteht darin, dass in den verschiedenen Phasen des Systemlebenszyklus eine höhere Effizienz erzielt wird:

  • Design : Die Methoden eines Objekts können schnell angegeben und für alle betroffenen Entwickler veröffentlicht werden
  • Entwicklung : Der Java-Compiler garantiert, dass alle Methoden der Schnittstelle mit der richtigen Signatur implementiert werden und dass alle Änderungen an der Schnittstelle für andere Entwickler sofort sichtbar sind
  • Integration : Es besteht die Möglichkeit, Klassen oder Subsysteme aufgrund ihrer gut etablierten Schnittstellen schnell miteinander zu verbinden
  • Testen : Schnittstellen helfen dabei, Fehler zu isolieren, da sie den Umfang eines möglichen Logikfehlers auf eine bestimmte Teilmenge von Methoden beschränken

Aufgrund der erforderlichen Code-Infrastruktur ist mit dieser Entwicklungstechnik ein gewisser Aufwand verbunden. Diese Infrastruktur enthält beide Schnittstellen für die Interaktionen zwischen Objekten und den Aufrufcode, um Implementierungen von Schnittstellen zu erstellen. Dieser Overhead ist im Vergleich zu der beschriebenen Einfachheit und dem einfachen Nutzen der Verwendung von Schnittstellen unbedeutend.

Grundlegendes Beispiel

Um das Konzept der Codierung für Schnittstellen weiter zu erläutern, habe ich ein einfaches Beispiel erstellt. Obwohl dieses Beispiel eindeutig trivial ist, zeigt es einige der oben genannten Vorteile.

Betrachten Sie das einfache Beispiel einer Klasse Car, die die Schnittstelle implementiert Vehicle. Die Schnittstelle Vehiclehat eine einzige Methode namens start(). Die Klasse Carimplementiert die Schnittstelle durch Bereitstellung einer start()Methode. Andere Funktionen in der CarKlasse wurden aus Gründen der Übersichtlichkeit weggelassen.

Schnittstelle Fahrzeug {// Alle Fahrzeugimplementierungen müssen die Startmethode public void start () implementieren. } Klasse Auto implementiert Fahrzeug {// Erforderlich, um Fahrzeug zu implementieren public void start () {...}}

Nachdem Carwir den Grundstein für das Objekt gelegt haben, können wir ein weiteres Objekt namens erstellen Valet. Es ist die ValetAufgabe des, das zu starten Carund es dem Restaurantpatron zu bringen. Das ValetObjekt kann wie folgt ohne Schnittstellen geschrieben werden:

Klasse Valet {öffentliches Auto getCar (Auto c) {...}} 

Das ValetObjekt hat eine Methode namens getCar, die ein CarObjekt zurückgibt . Dieses Codebeispiel erfüllt die funktionalen Anforderungen des Systems, verbindet das ValetObjekt jedoch für immer mit dem des Car. In dieser Situation sollen die beiden Objekte eng miteinander verbunden sein. Das ValetObjekt erfordert Kenntnisse des CarObjekts und hat Zugriff auf alle öffentlichen Methoden und Variablen, die in diesem Objekt enthalten sind. Es ist am besten, eine derart enge Kopplung von Code zu vermeiden, da dies die Abhängigkeiten erhöht und die Flexibilität verringert.

Um das ValetObjekt mithilfe von Schnittstellen zu codieren , kann die folgende Implementierung verwendet werden:

Klasse Valet {öffentliches Fahrzeug getVehicle (Fahrzeug c) {...}} 

Während die Codeänderungen relativ geringfügig sind und die Referenzen von Carauf geändert werden, sind Vehicledie Auswirkungen auf den Entwicklungszyklus beträchtlich. Bei Verwendung der zweiten Implementierung Valetkennt der nur die in der VehicleSchnittstelle definierten Methoden und Variablen . Alle anderen öffentlichen Methoden und Daten, die in der spezifischen Implementierung der VehicleSchnittstelle enthalten sind, sind dem Benutzer des VehicleObjekts verborgen .

Diese einfache Codeänderung hat die ordnungsgemäße Verschleierung von Informationen und die Implementierung vor anderen Objekten sichergestellt und daher die Möglichkeit ausgeschlossen, dass Entwickler unerwünschte Methoden verwenden.

Erstellen des Schnittstellenobjekts

Das letzte Thema, das in Bezug auf diese Entwicklungstechnik erörtert werden muss, ist die Erstellung der Schnittstellenobjekte. Während es möglich ist, eine neue Instanz einer Klasse mit dem newOperator zu erstellen , ist es nicht möglich, eine Instanz einer Schnittstelle direkt zu erstellen. Um eine Schnittstellenimplementierung zu erstellen, müssen Sie das Objekt instanziieren und in die gewünschte Schnittstelle umwandeln. Daher kann der Entwickler, dem der Objektcode gehört, sowohl für die Erstellung der Instanz des Objekts als auch für die Durchführung des Castings verantwortlich sein.

Dieser Erstellungsprozess kann mithilfe eines FactoryMusters erreicht werden , bei dem ein externes Objekt eine statische createXYZ()Methode für a aufruft Factoryund eine Schnittstelle zurückgibt. Dies kann auch erreicht werden, wenn ein Entwickler eine Methode für ein anderes Objekt aufruft und ihm anstelle der eigentlichen Klasse eine Schnittstelle übergibt. Dies wäre analog zum Übergeben einer EnumerationSchnittstelle anstelle eines Vectoroder Hashtable.

Detailliertes Beispiel

Um die Verwendung dieses Schemas in einem größeren Projekt zu demonstrieren, habe ich das Beispiel eines Besprechungsplaners erstellt. Dieser Planer besteht aus drei Hauptkomponenten: den Ressourcen (Konferenzraum und Besprechungsteilnehmer), dem Ereignis (der Besprechung selbst) und dem Planer (derjenige, der den Ressourcenkalender verwaltet).

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.

Ein weiterer Vorteil ist die zentrale Position des Codes. Wenn der ResourceSchnittstelle neue Methoden hinzugefügt werden sollen, werden alle Implementierungen dieser Schnittstelle als änderungsbedürftig identifiziert. Dadurch wird die Untersuchung reduziert, die erforderlich ist, um die möglichen Auswirkungen von Änderungen an der Schnittstelle zu ermitteln.

Zusätzlich zu den Entwicklungsvorteilen bietet die in diesem Artikel vorgestellte Technik dem Projektmanagement die Gewissheit, dass während des gesamten Entwicklungszyklus Kommunikationsmuster zwischen Objekten oder Systemen eingerichtet und durchgesetzt wurden. Dies reduziert das Ausfallrisiko während der Integrations- und Testphase des Projekts.