Kommandomønsteret i Java

1. Oversigt

Kommandomønsteret er et adfærdsmæssigt designmønster og er en del af GoFs formelle liste over designmønstre. Kort sagt, mønsteret har til hensigt at indkapsle i et objekt alle de data, der kræves for at udføre en given handling (kommando), herunder hvilken metode der skal kaldes, metodens argumenter og det objekt, metoden tilhører.

Denne model tillader os at afkoble objekter, der producerer kommandoerne fra deres forbrugere, så det er derfor, mønsteret er almindeligt kendt som producent-forbruger mønster.

I denne vejledning lærer vi, hvordan vi implementerer kommandomønsteret i Java ved hjælp af både objektorienterede og objektfunktionelle tilgange, og vi ser i hvilke brugstilfælde det kan være nyttigt.

2. Objektorienteret implementering

I en klassisk implementering kræver kommandomønsteret implementering af fire komponenter: Kommandoen, Modtageren, Invoker og Klienten.

Lad os oprette et grundlæggende eksempel for at forstå, hvordan mønsteret fungerer, og hvilken rolle hver komponent spiller.

Lad os antage, at vi vil udvikle en tekstfilapplikation. I et sådant tilfælde skal vi implementere al funktionalitet, der kræves til at udføre nogle tekstfilrelaterede operationer, såsom åbning, skrivning, lagring af en tekstfil osv.

Så vi skal nedbryde applikationen i de fire ovennævnte komponenter.

2.1. Kommandoklasser

En kommando er et objekt, hvis rolle er at gemme alle de oplysninger, der kræves for at udføre en handling, herunder metoden, der skal kaldes, metodeargumenterne og objektet (kendt som modtageren), der implementerer metoden.

For at få en mere nøjagtig idé om, hvordan kommandoobjekter fungerer, lad os begynde at udvikle et simpelt kommandolag, der kun indeholder en enkelt grænseflade og to implementeringer:

@FunctionalInterface offentlig grænseflade TextFileOperation {String execute (); }
offentlig klasse OpenTextFileOperation implementerer TextFileOperation {privat 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 tilfælde er TextFileOperation interface definerer kommandoobjektenes API og de to implementeringer, OpenTextFileOperation og SaveTextFileOperation, udføre de konkrete handlinger. Førstnævnte åbner en tekstfil, mens sidstnævnte gemmer en tekstfil.

Det er klart at se funktionaliteten af ​​et kommandoobjekt: TextFileOperation kommandoer indkapsle alle de krævede oplysninger til åbning og lagring af en tekstfil, herunder modtagerobjektet, metoderne til at ringe til og argumenterne (i dette tilfælde kræves ingen argumenter, men de kan være).

Det er værd at understrege det den komponent, der udfører filhandlingerne, er modtageren ( TextFile eksempel).

2.2. Modtagerklassen

En modtager er et objekt, der udfører et sæt sammenhængende handlinger. Det er den komponent, der udfører den aktuelle handling, når kommandoen er udføre () metode kaldes.

I dette tilfælde skal vi definere en modtagerklasse, hvis rolle er at modellere TextFile genstande:

offentlig klasse TextFile {privat strengnavn; // constructor public String open () {return "Åbningsfil" + navn; } offentlig streng gemme () {return "Gem fil" + navn; } // yderligere tekstfilmetoder (redigering, skrivning, kopiering, indsættelse)} 

2.3. Invoker-klassen

En invoker er et objekt, der ved, hvordan man udfører en given kommando, men ved ikke, hvordan kommandoen er implementeret. Det kender kun kommandos grænseflade.

I nogle tilfælde gemmer og køber også kommandoer, bortset fra at udføre dem. Dette er nyttigt til implementering af nogle yderligere funktioner, såsom makrooptagelse eller fortryd og gentag funktionalitet.

I vores eksempel bliver det tydeligt, at der skal være en ekstra komponent, der er ansvarlig for at påberåbe sig kommandoobjekterne og udføre dem gennem kommandoerne ' udføre () metode. Det er her, invoker-klassen kommer i spil.

Lad os se på en grundlæggende implementering af vores invoker:

offentlig klasse TextFileOperationExecutor {privat endelig Liste textFileOperations = ny ArrayList (); public String executeOperation (TextFileOperation textFileOperation) {textFileOperations.add (textFileOperation); returner textFileOperation.execute (); }}

Det TextFileOperationExecutor klasse er bare en tyndt lag af abstraktion, der afkobler kommandoobjekterne fra deres forbrugere og kalder metoden indkapslet i TextFileOperation kommandoobjekter.

I dette tilfælde gemmer klassen også kommandoobjekterne i a Liste. Selvfølgelig er dette ikke obligatorisk i mønsterimplementeringen, medmindre vi har brug for at tilføje yderligere kontrol til operationernes udførelsesproces.

2.4. Klientklassen

En klient er et objekt, der styrer kommandoen eksekvering proces ved at specificere, hvilke kommandoer der skal udføres, og på hvilke stadier af processen de skal udføres.

Så hvis vi vil være ortodokse med mønsterets formelle definition, skal vi oprette en klientklasse ved hjælp af det typiske vigtigste metode:

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

3. Objektfunktionel implementering

Indtil videre har vi brugt en objektorienteret tilgang til at implementere kommandomønsteret, som alt sammen er godt og godt.

Fra Java 8 kan vi bruge en objektfunktionel tilgang, baseret på lambda-udtryk og metodereferencer, til gør koden lidt mere kompakt og mindre detaljeret.

3.1. Brug af Lambda-udtryk

Som den TextFileOperation interface er en funktionel grænseflade, det kan vi videregive kommandoobjekter i form af lambda-udtryk til påkalderenuden at skulle oprette TextFileOperation tilfælde eksplicit:

TextFileOperationExecutor textFileOperationExecutor = ny TextFileOperationExecutor (); textFileOperationExecutor.executeOperation (() -> "Åbning af fil file1.txt"); textFileOperationExecutor.executeOperation (() -> "Lagring af fil file1.txt"); 

Implementeringen ser nu meget mere strømlinet og kortfattet ud, som vi har gjort reduceret mængden af ​​kedelpladekode.

Alligevel står spørgsmålet stadig: er denne tilgang bedre sammenlignet med den objektorienterede?

Nå, det er vanskeligt. Hvis vi antager, at mere kompakt kode betyder bedre kode i de fleste tilfælde, så er det faktisk.

Som en tommelfingerregel skal vi evaluere på basis af hvert tilfælde, hvornår vi skal ty til lambda-udtryk.

3.2. Brug af metodehenvisninger

På samme måde kan vi bruge metodereferencer til videregivelse af kommandoobjekter til påkalderen:

TextFileOperationExecutor textFileOperationExecutor = ny TextFileOperationExecutor (); TextFile textFile = ny TextFile ("file1.txt"); textFileOperationExecutor.executeOperation (textFile :: åben); textFileOperationExecutor.executeOperation (textFile :: gem); 

I dette tilfælde er implementeringen lidt mere detaljeret end den, der bruger lambdas, da vi stadig var nødt til at oprette TextFile tilfælde.

4. Konklusion

I denne artikel lærte vi kommandomønstrets nøglekoncepter og hvordan man implementerer mønsteret i Java ved hjælp af en objektorienteret tilgang og en kombination af lambda-udtryk og metodereferencer.

Som normalt er alle kodeeksemplerne vist i denne vejledning tilgængelige på GitHub.


$config[zx-auto] not found$config[zx-overlay] not found