Het opdrachtpatroon in Java

1. Overzicht

Het opdrachtpatroon is een gedragsmatig ontwerppatroon en maakt deel uit van de officiële lijst met ontwerppatronen van de GoF. Simpel gezegd, het patroon is van plan inkapselen in een object alle gegevens die nodig zijn voor het uitvoeren van een bepaalde actie (commando), inclusief welke methode moet worden aangeroepen, de argumenten van de methode en het object waartoe de methode behoort.

Dit model stelt ons in staat ontkoppel objecten die de opdrachten van hun consumenten produceren, dus daarom staat het patroon algemeen bekend als het producent-consumentpatroon.

In deze zelfstudie leren we hoe we het opdrachtpatroon in Java kunnen implementeren door zowel objectgeoriënteerde als objectfunctionele benaderingen te gebruiken, en we zullen zien in welke gevallen het nuttig kan zijn.

2. Objectgeoriënteerde implementatie

In een klassieke implementatie vereist het commandopatroon implementatie van vier componenten: het Commando, de Ontvanger, de Invoker en de Client.

Laten we een eenvoudig voorbeeld maken om te begrijpen hoe het patroon werkt en welke rol elk onderdeel speelt.

Stel dat we een tekstbestandstoepassing willen ontwikkelen. In dat geval moeten we alle functionaliteit implementeren die nodig is voor het uitvoeren van sommige tekstbestandgerelateerde bewerkingen, zoals openen, schrijven, een tekstbestand opslaan, enzovoort.

We moeten de applicatie dus opsplitsen in de vier hierboven genoemde componenten.

2.1. Command Klassen

Een commando is een object waarvan de rol to is sla alle informatie op die nodig is om een ​​actie uit te voeren, inclusief de methode die moet worden aangeroepen, de methode-argumenten en het object (bekend als de ontvanger) dat de methode implementeert.

Om een ​​nauwkeuriger idee te krijgen van hoe opdrachtobjecten werken, gaan we beginnen met het ontwikkelen van een eenvoudige opdrachtlaag die slechts één enkele interface en twee implementaties bevat:

@FunctionalInterface openbare interface TextFileOperation {String execute (); }
openbare klasse OpenTextFileOperation implementeert TextFileOperation {privé TextFile textFile; // constructors @Override public String execute () {return textFile.open (); }}
openbare klasse SaveTextFileOperation implementeert TextFileOperation {// hetzelfde veld en dezelfde constructor als hierboven @Override public String execute () {return textFile.save (); }} 

In dit geval is het TextFileOperation interface definieert de API van de opdrachtobjecten en de twee implementaties, OpenTextFileOperation en SaveTextFileOperation, voer de concrete acties uit. De eerste opent een tekstbestand, terwijl de laatste een tekstbestand opslaat.

Het is duidelijk om de functionaliteit van een commando-object te zien: het TextFileOperation commando's bevatten alle benodigde informatie voor het openen en opslaan van een tekstbestand, inclusief het ontvangerobject, de aan te roepen methoden en de argumenten (in dit geval zijn geen argumenten vereist, maar dat zouden ze wel kunnen zijn).

Het is de moeite waard om dat te benadrukken de component die de bestandsbewerkingen uitvoert, is de ontvanger (het Tekstbestand voorbeeld).

2.2. De ontvangerklasse

Een ontvanger is een object dat voert een reeks samenhangende acties uit. Het is de component die de daadwerkelijke actie uitvoert wanneer het commando uitvoeren () methode wordt genoemd.

In dit geval moeten we een ontvangerklasse definiëren, wiens rol het is om te modelleren Tekstbestand voorwerpen:

public class TextFile {private String naam; // constructor public String open () {return "Openingsbestand" + naam; } public String save () {return "Bestand opslaan" + naam; } // aanvullende methoden voor tekstbestanden (bewerken, schrijven, kopiëren, plakken)} 

2.3. De Invoker-klasse

Een invoker is een object dat weet hoe een bepaald commando moet worden uitgevoerd, maar weet niet hoe het commando is geïmplementeerd. Het kent alleen de interface van de opdracht.

In sommige gevallen slaat de invoker ook opdrachten op en zet ze in de wachtrij, behalve dat hij ze uitvoert. Dit is handig voor het implementeren van enkele extra functies, zoals macro-opname of functionaliteit voor ongedaan maken en opnieuw uitvoeren.

In ons voorbeeld wordt het duidelijk dat er een extra component moet zijn die verantwoordelijk is voor het aanroepen van de opdrachtobjecten en het uitvoeren ervan via de opdrachten ' uitvoeren () methode. Dit is precies waar de invokerklasse in het spel komt.

Laten we eens kijken naar een basisimplementatie van onze invoker:

openbare klasse TextFileOperationExecutor {privé definitieve lijst textFileOperations = nieuwe ArrayList (); openbare String executeOperation (TextFileOperation textFileOperation) {textFileOperations.add (textFileOperation); return textFileOperation.execute (); }}

De TextFileOperationExecutor klasse is gewoon een dunne laag abstractie die de opdrachtobjecten loskoppelt van hun consumenten en roept de methode aan die is ingekapseld in de TextFileOperation commando objecten.

In dit geval slaat de klasse de opdrachtobjecten ook op in een Lijst. Dit is natuurlijk niet verplicht in de patroonimplementatie, tenzij we wat meer controle moeten toevoegen aan het uitvoeringsproces van de operaties.

2.4. De cliëntklasse

Een klant is een object dat regelt het uitvoerproces van de opdracht door te specificeren welke opdrachten moeten worden uitgevoerd en in welke stadia van het proces ze moeten worden uitgevoerd.

Dus als we orthodox willen zijn met de formele definitie van het patroon, moeten we een cliëntklasse creëren door de typische te gebruiken hoofd methode:

openbare statische leegte hoofd (String [] args) {TextFileOperationExecutor textFileOperationExecutor = nieuwe TextFileOperationExecutor (); textFileOperationExecutor.executeOperation (nieuwe OpenTextFileOperation (nieuwe TextFile ("file1.txt")))); textFileOperationExecutor.executeOperation (nieuwe SaveTextFileOperation (nieuwe TextFile ("file2.txt")))); } 

3. Object-functionele implementatie

Tot dusver hebben we een objectgeoriënteerde benadering gebruikt om het commandopatroon te implementeren, wat allemaal goed en wel is.

Vanaf Java 8 kunnen we een objectfunctionele benadering gebruiken, gebaseerd op lambda-expressies en methodeverwijzingen, naar maak de code een beetje compacter en minder uitgebreid.

3.1. Lambda-expressies gebruiken

Zoals de TextFileOperation interface is een functionele interface, dat kunnen we geef commando-objecten in de vorm van lambda-expressies door aan de invoker, zonder dat u het TextFileOperation instanties expliciet:

TextFileOperationExecutor textFileOperationExecutor = nieuwe TextFileOperationExecutor (); textFileOperationExecutor.executeOperation (() -> "Openen bestand file1.txt"); textFileOperationExecutor.executeOperation (() -> "Bestand file1.txt opslaan"); 

De implementatie ziet er nu veel gestroomlijnder en beknopter uit, net als wij verminderde de hoeveelheid boilerplate-code.

Toch blijft de vraag bestaan: is deze benadering beter vergeleken met de objectgeoriënteerde benadering?

Nou, dat is lastig. Als we aannemen dat compactere code in de meeste gevallen betere code betekent, dan is dat inderdaad zo.

Als vuistregel moeten we per gebruik evalueren wanneer we onze toevlucht moeten nemen tot lambda-expressies.

3.2. Methodeverwijzingen gebruiken

Evenzo kunnen we methodeverwijzingen gebruiken voor commando-objecten doorgeven aan de invoker:

TextFileOperationExecutor textFileOperationExecutor = nieuwe TextFileOperationExecutor (); TextFile textFile = nieuw TextFile ("file1.txt"); textFileOperationExecutor.executeOperation (textFile :: open); textFileOperationExecutor.executeOperation (textFile :: save); 

In dit geval is de implementatie een beetje meer uitgebreid dan degene die lambda's gebruikt, aangezien we nog steeds het Tekstbestand gevallen.

4. Conclusie

In dit artikel hebben we de belangrijkste concepten van het opdrachtpatroon geleerd en hoe het patroon in Java kan worden geïmplementeerd door een objectgeoriënteerde benadering en een combinatie van lambda-expressies en methodeverwijzingen te gebruiken.

Zoals gewoonlijk zijn alle codevoorbeelden die in deze tutorial worden getoond, beschikbaar op GitHub.