Kommandomønsteret i Java

1. Oversikt

Kommandomønsteret er et atferdsmønster og er en del av GoFs formelle liste over designmønstre. Enkelt sagt, mønsteret har til hensikt å kapsle inn i et objekt alle dataene som kreves for å utføre en gitt handling (kommando), inkludert hvilken metode du skal kalle, metodens argumenter og objektet som metoden tilhører.

Denne modellen lar oss koble fra objekter som produserer kommandoene fra forbrukerne, så det er derfor mønsteret er kjent som produsent-forbruker mønster.

I denne opplæringen lærer vi hvordan vi implementerer kommandomønsteret i Java ved å bruke både objektorienterte og objektfunksjonelle tilnærminger, og vi vil se i hvilke brukssaker det kan være nyttig.

2. Objektorientert implementering

I en klassisk implementering krever kommandomønsteret implementering av fire komponenter: kommandoen, mottakeren, innkalleren og klienten.

For å forstå hvordan mønsteret fungerer og hvilken rolle hver komponent spiller, la oss lage et grunnleggende eksempel.

La oss anta at vi ønsker å utvikle et tekstfilprogram. I et slikt tilfelle bør vi implementere all funksjonalitet som kreves for å utføre noen tekstfilrelaterte operasjoner, for eksempel å åpne, skrive, lagre en tekstfil og så videre.

Så vi bør dele opp applikasjonen i de fire komponentene som er nevnt ovenfor.

2.1. Kommandoklasser

En kommando er et objekt hvis rolle er å lagre all informasjonen som kreves for å utføre en handling, inkludert metoden for å ringe, metodeargumentene og objektet (kjent som mottakeren) som implementerer metoden.

For å få en mer nøyaktig ide om hvordan kommandoobjekter fungerer, la oss begynne å utvikle et enkelt kommandolag som inneholder bare ett enkelt grensesnitt og to implementeringer:

@FunctionalInterface offentlig grensesnitt TextFileOperation {String execute (); }
offentlig klasse OpenTextFileOperation implementerer TextFileOperation {private TextFile textFile; // constructors @Override public String execute () {return textFile.open (); }}
offentlig klasse SaveTextFileOperation implementerer TextFileOperation {// samme felt og konstruktør som ovenfor @Override public String execute () {return textFile.save (); }} 

I dette tilfellet TextFileOperation grensesnitt definerer kommandoobjektenes API og de to implementeringene, OpenTextFileOperation og SaveTextFileOperation, utføre de konkrete handlingene. Førstnevnte åpner en tekstfil, mens sistnevnte lagrer en tekstfil.

Det er klart å se funksjonaliteten til et kommandoobjekt: TextFileOperation kommandoer kapsle all nødvendig informasjon for å åpne og lagre en tekstfil, inkludert mottakerobjektet, metodene for å ringe og argumentene (i dette tilfellet kreves ingen argumenter, men de kan være).

Det er verdt å understreke det komponenten som utfører filoperasjonene er mottakeren ( TextFile forekomst).

2.2. Mottakerklassen

En mottaker er et objekt som utfører et sett med sammenhengende handlinger. Det er komponenten som utfører den faktiske handlingen når kommandoen er henrette() metoden kalles.

I dette tilfellet må vi definere en mottakerklasse, hvis rolle er å modellere TextFile gjenstander:

offentlig klasse TextFile {privat strengnavn; // constructor public String open () {return "Åpningsfil" + navn; } public String save () {return "Saving file" + name; } // tilleggstekst-filmetoder (redigering, skriving, kopiering, liming)} 

2.3. Invoker-klassen

En påkaller er et objekt som vet hvordan man skal utføre en gitt kommando, men vet ikke hvordan kommandoen er implementert. Den kjenner bare kommandogrensesnittet.

I noen tilfeller lagrer og køer også kommandoer, bortsett fra å utføre dem. Dette er nyttig for å implementere noen tilleggsfunksjoner, for eksempel makroopptak eller angre og gjenta funksjonalitet.

I vårt eksempel blir det tydelig at det må være en ekstra komponent som er ansvarlig for å påkalle kommandoobjektene og utføre dem gjennom kommandoene ' henrette() metode. Dette er akkurat der innkallerklassen spiller inn.

La oss se på en grunnleggende implementering av innkalleren vår:

public class TextFileOperationExecutor {private final List textFileOperations = new ArrayList (); public String executeOperation (TextFileOperation textFileOperation) {textFileOperations.add (textFileOperation); returner textFileOperation.execute (); }}

De TextFileOperationExecutor klasse er bare en tynt lag med abstraksjon som avkobler kommandoobjektene fra forbrukerne og kaller metoden innkapslet i TextFileOperation kommandoobjekter.

I dette tilfellet lagrer klassen også kommandoobjektene i a Liste. Dette er selvfølgelig ikke obligatorisk i mønsterimplementeringen, med mindre vi trenger å legge til litt ytterligere kontroll i operasjonenes utførelsesprosess.

2.4. Klientklassen

En klient er et objekt som styrer kommandoutførelsesprosessen ved å spesifisere hvilke kommandoer som skal utføres og på hvilke stadier av prosessen for å utføre dem.

Så hvis vi ønsker å være ortodokse med mønsterets formelle definisjon, må vi opprette en klientklasse ved å bruke det typiske hoved- metode:

public static void main (String [] args) {TextFileOperationExecutor textFileOperationExecutor = new TextFileOperationExecutor (); textFileOperationExecutor.executeOperation (ny OpenTextFileOperation (ny TextFile ("file1.txt"))); textFileOperationExecutor.executeOperation (ny SaveTextFileOperation (ny TextFile ("file2.txt")))); } 

3. Objekt-funksjonell implementering

Så langt har vi brukt en objektorientert tilnærming for å implementere kommandomønsteret, som er bra og bra.

Fra Java 8 kan vi bruke en objektfunksjonell tilnærming, basert på lambdauttrykk og metodereferanser, til gjør koden litt mer kompakt og mindre ordentlig.

3.1. Bruke Lambda Expressions

Som den TextFileOperation grensesnitt er et funksjonelt grensesnitt, det kan vi gi kommandoobjekter i form av lambdauttrykk til innkallereren, uten å måtte opprette TextFileOperation eksplisitt eksplisitt:

TextFileOperationExecutor textFileOperationExecutor = ny TextFileOperationExecutor (); textFileOperationExecutor.executeOperation (() -> "Åpningsfil file1.txt"); textFileOperationExecutor.executeOperation (() -> "Lagrer fil file1.txt"); 

Gjennomføringen ser nå ut som mye mer strømlinjeformet og konsis redusert mengden kjeleplatekode.

Allikevel står spørsmålet fremdeles: er denne tilnærmingen bedre, sammenlignet med den objektorienterte?

Vel, det er vanskelig. Hvis vi antar at mer kompakt kode i de fleste tilfeller betyr bedre kode, så er det faktisk.

Som en tommelfingerregel bør vi evaluere per bruk-tilfelle når vi skal ty til lambdauttrykk.

3.2. Bruke metodereferanser

På samme måte kan vi bruke metodereferanser for overføring av kommandoobjekter til innkalleren:

TextFileOperationExecutor textFileOperationExecutor = ny TextFileOperationExecutor (); TextFile textFile = ny TextFile ("file1.txt"); textFileOperationExecutor.executeOperation (textFile :: open); textFileOperationExecutor.executeOperation (textFile :: lagre); 

I dette tilfellet er implementeringen litt mer ordentlig enn den som bruker lambdas, ettersom vi fremdeles måtte lage TextFile tilfeller.

4. Konklusjon

I denne artikkelen lærte vi kommandomønsterets nøkkelbegreper og hvordan man implementerer mønsteret i Java ved å bruke en objektorientert tilnærming og en kombinasjon av lambdauttrykk og metodereferanser.

Som vanlig er alle kodeeksemplene vist i denne opplæringen tilgjengelig på GitHub.