Polecenie

Wzorzec Polecenie jest to operacyjny wzorzec projektowy, dzięki któremu jesteśmy w stanie wyciągnąć żądanie do osobnego obiektu i je zahermetyzować. Daje to możliwość parametryzowania różnych obiektów żądaniami, a także implementować akcje, które można wycofać.

Kiedy stosować wzorzec Polecenie?

Wyobraźmy sobie sytuację, w której inwestor pewnego przedsięwzięcia budowlanego zatrudnia kierownika do prowadzenia budowy. Kierownik ten ma do wykorzystania kilku różnych pracowników, z których każdy wykonuje jakieś swoje zadanie we właściwy dla siebie sposób. Kierownik może zlecać zadania bezpośrednio do swoich pracowników. Musi być jednak wówczas w pełni świadomy sposobu w jaki każdy z nich wykonuje swoją pracę. Sprawi to, że kierownik będzie musiał znać szczegóły dotyczące wykonywania danych zadań. My natomiast chcemy uzyskać sytuacje, w której kierownik po prostu jest w stanie zlecać dane żądania, a one będą przekazane do odpowiednich pracowników, którzy je wykonają. Kierownika nie interesuje ani kto, ani w jaki sposób wykona pracę. Liczy się, żeby został spełniony efekt końcowy.

I tutaj z pomocą przychodzi nam Polecenie. Otóż jesteśmy w stanie wydzielić żądanie do osobnego obiektu implementującego interfejs Polecenie. Taki interfejs zawiera zazwyczaj dwie metody: wykonaj oraz cofnij.

public interface Polecenie {
    void wykonaj();
    
    void wycofaj();
}

Każdy obiekt polecenie implementuje powyższy interfejs. Ponadto do obiektu Polecenie przekazujemy obiekt realizujący zlecone zadanie. W naszym przypadku utworzymy klasy Pracownik oraz Sprzątaczka, które będą obiektami realizującymi i przekażemy je do odpowiednich poleceń. Kod wygląda następująco:

public class Pracownik {
    public void zbudujMost() {
        System.out.println("Most Zbudowany!!!");
    }

    public void zdemontujMost() {
        System.out.println("Most zdemontowany!!!");
    }
}
public class Sprzataczka {
    public void sprzataj() {
        System.out.println("Posprzatane!!!");
    }

    public void brudz() {
        System.out.println("Nabrudzone!!!");
    }
}

A następnie tworzymy odpowiednie obiekty Polecenie:

public class PolecenieZbudujMost implements Polecenie{

    Pracownik pracownik;

    public PolecenieZbudujMost(Pracownik pracownik) {
        this.pracownik = pracownik;
    }

    public void setPracownik(Pracownik pracownik) {
        this.pracownik = pracownik;
    }

    @Override
    public void wykonaj() {
        pracownik.zbudujMost();
    }

    @Override
    public void wycofaj() {
        pracownik.zdemontujMost();
    }
}
public class PolecenieSprzataj implements Polecenie{
    Sprzataczka sprzataczka;

    public PolecenieSprzataj(Sprzataczka sprzataczka) {
        this.sprzataczka = sprzataczka;
    }

    public void setSprzataczka(Sprzataczka sprzataczka) {
        this.sprzataczka = sprzataczka;
    }

    @Override
    public void wykonaj() {
        sprzataczka.sprzataj();
    }

    @Override
    public void wycofaj() {
        sprzataczka.brudz();
    }
}

Zauważyć należy, że teraz to obiekt polecenie wie, do którego obiektu należy delegować wykonanie żądania. Do obiektu Kierownik zostanie przekazane jedynie polecenie, zatem będzie on nieświadomy ani kto ani jak je wykona.

public class Kierownik {

    Polecenie polecenie;
    Polecenie wycofajPolecenie;

    public Kierownik() {
        this.polecenie = new BrakPolecenia();
        this.wycofajPolecenie = new BrakPolecenia();
    }

    public void setPolecenie(Polecenie polecenie) {
        this.polecenie = polecenie;
    }

    void uruchom () {
        polecenie.wykonaj();
        wycofajPolecenie = polecenie;
    }

    void cofnij() {
        wycofajPolecenie.wycofaj();
    }
}

Jak widać Kierownik paramteryzowany jest Poleceniem. Ciekawą rzeczą może być obiekt klasy BrakPolecenia, który również implementuje interfejs Polecenie, ale pozostawia ciała metod puste.

public class BrakPolecenia implements Polecenie{
    @Override
    public void wykonaj() {

    }

    @Override
    public void wycofaj() {

    }
}

Dzięki temu nie musimy budować, żadnych konstrukcji warunkowych na wypadek gdyby nie było przypisanego żadnego Polecenia. Po prostu na początku przypisać Polecenie, które nie robi nic.

Wracając do kierownika, to można zmieniać dynamicznie w trakcie trwania programu przypisane mu Polecenia, Można również stworzyć trochę większą wersję obiektu Kierownik, który przechowuje np tablicę Poleceń. Zasada jest jednak taka, że kierownik został totalnie odseparowany od Pracowników, dzięki czemu nie musi mieć żadnej świadomości, do jakiego obiektu zostanie oddelegowane żądanie, ani jak dana operacja jest implementowana. Zobaczmy klasę Inwestor, w której wykonujemy metodę main:

public class Inwestor {


    public static void main(String[] args) {
        Pracownik janusz = new Pracownik();
        Polecenie zbudujMost = new PolecenieZbudujMost(janusz);

        Sprzataczka grazyna = new Sprzataczka();
        Polecenie sprzatnij = new PolecenieSprzataj(grazyna);

        Kierownik kierownik = new Kierownik();

        kierownik.setPolecenie(zbudujMost);

        kierownik.uruchom();
        kierownik.cofnij();

        kierownik.setPolecenie(sprzatnij);

        kierownik.uruchom();
        kierownik.cofnij();
    }
}

Jak widać Inwestor będący Klientem jest odpowiedzialny za stworzenie wszystkich obiektów: Kierownika, Pracowników oraz Poleceń i przypisania Poleceniom ich obiektów realizujących, czyli Pracowników. Następnie Polecenia są przypisywane Kierownikowi, który potrafi je wywoływać oraz cofać. Udało się zrealizować wzorzec Polecenie !!!.

Podsumowanie

Wzorzec Polecenie pozwala na hermetyzacje żądań do osobnych obiektów i odseparowanie obiektu wywołującego od obiektu realizującego żądanie. Można uzyskać dzięki temu kilka wymiernych korzyści. Możemy parametryzować obiekty żądaniami oraz zmieniać je dynamicznie w trakcie programu. Możemy zaimplementować mechanizm cofania, a także opóźniać wykonywanie żądań. Nic nie stoi na przeszkodzie by tworzyć złożone makro polecenia składające się z kilku mniejszych poleceń. Ponadto postępujemy zgodnie z paradygmatami programowania obiektowego. Mianowicie mamy tu spełnioną zasadę pojedynczej odpowiedzialności. Polecenie Kierownik ma zlecać wykonanie żądania, Polecenie przekazywać zlecenie do obiektu realizującego a ten je wykonać. Spełniona jest również zasada otwarte/zamknięte. Można rozbudowywać program o kolejne Polecenia bez modyfikacji istniejącego kodu.