Wzorzec polecenia - Command pattern

W programowaniu obiektowym The pattern komenda jest behawioralny wzorzec projektowy , w którym obiekt jest używany do hermetyzacji wszystkie informacje potrzebne do przeprowadzenia działania lub wywołać zdarzenie w późniejszym czasie. Informacje te obejmują nazwę metody, obiekt, który jest właścicielem metody i wartości parametrów metody.

Cztery terminy zawsze związane z wzorcem polecenia to polecenie , odbiorca , wywołujący i klient . Obiekt polecenia wie o odbiorniku i wywołuje metodę odbiornika. W poleceniu zapisywane są wartości parametrów metody odbiornika. Obiekt odbiornika do wykonania tych metod jest również przechowywany w obiekcie polecenia przez agregację . Odbiornik następnie wykonuje pracę, gdy execute()metoda w poleceniu jest tzw. Obiekt wywołujący wie, jak wykonać polecenie i opcjonalnie prowadzi księgowość dotyczącą wykonania polecenia. Wywołujący nie wie nic o konkretnej komendzie, wie tylko o interfejsie komend . Obiekty wywołujące , obiekty poleceń i obiekty odbierające są przechowywane przez obiekt klienta , klient decyduje, które obiekty odbierające przypisuje do obiektów poleceń, a które polecenia przypisuje do obiektu wywołującego. Klient decyduje, które polecenia wykonać, w którym momencie. Aby wykonać polecenie, przekazuje obiekt polecenia do obiektu wywołującego.

Korzystanie z obiektów poleceń ułatwia konstruowanie ogólnych komponentów, które muszą delegować, sekwencjonować lub wykonywać wywołania metod w wybranym przez siebie czasie, bez konieczności znajomości klasy metody lub jej parametrów. Korzystanie z obiektu invoker pozwala na wygodne prowadzenie ksiąg dotyczących wykonywania poleceń, a także implementowanie różnych trybów dla poleceń, którymi zarządza obiekt invoker, bez potrzeby, aby klient był świadomy istnienia księgowości lub trybów.

Główne idee tego wzorca projektowego ściśle odzwierciedlają semantykę funkcji pierwszej klasy i funkcji wyższego rzędu w funkcjonalnych językach programowania . W szczególności obiekt wywołujący jest funkcją wyższego rzędu, której obiekt polecenia jest argumentem pierwszej klasy.

Przegląd

Wzorzec projektowy poleceń jest jednym z dwudziestu trzech dobrze znanych wzorców projektowych GoF, które opisują, jak rozwiązywać powtarzające się problemy projektowe w celu projektowania elastycznego i wielokrotnego użytku oprogramowania zorientowanego obiektowo, czyli obiektów, które są łatwiejsze do wdrożenia, zmiany, testowania i ponowne użycie.

Użycie wzorca projektowego polecenia może rozwiązać następujące problemy:

  • Należy unikać łączenia osoby wywołującej żądanie z konkretnym żądaniem. Oznacza to, że należy unikać żądań podłączonych na stałe.
  • Powinna istnieć możliwość skonfigurowania obiektu (wywołującego żądanie) z żądaniem.

Implementacja (hard-wiring) żądania bezpośrednio do klasy jest nieelastyczna, ponieważ łączy klasę z konkretnym żądaniem w czasie kompilacji, co uniemożliwia określenie żądania w czasie wykonywania.

Za pomocą wzorca projektowego polecenia opisano następujące rozwiązanie:

  • Zdefiniuj oddzielne (polecenia) obiekty, które hermetyzują żądanie.
  • Klasa deleguje żądanie do obiektu polecenia zamiast bezpośrednio implementować określone żądanie.

Dzięki temu można skonfigurować klasę z obiektem polecenia, który jest używany do wykonania żądania. Klasa nie jest już powiązana z konkretnym żądaniem i nie ma wiedzy (jest niezależna) na temat sposobu realizacji żądania.

Zobacz także diagram klas i sekwencji UML poniżej.

Struktura

Diagram klas i sekwencji UML

Przykładowy diagram klas i sekwencji UML dla wzorca projektowego Command.

W powyższym UML diagram klas The Invokerklasa nie realizuje wniosek bezpośrednio. Zamiast tego Invokerodnosi się do Commandinterfejsu do wykonania żądania ( command.execute()), co czyni Invokerniezależnym od tego, jak żądanie jest wykonywane. Te Command1narzędzia KLASY Commandinterfejs wykonując działania na odbiorniku ( receiver1.action1()).

UML schemat sekwencji przedstawia interakcje wykonywania czasu: Invokerpołączeń obiektu execute()na Command1obiekcie. Command1wzywa action1()na Receiver1obiekcie, który wykonuje żądania.

Diagram klas UML

Diagram UML wzorca poleceń

Używa

Przyciski GUI i pozycje menu
W programowaniu Swing i Borland Delphi , an Actionjest obiektem polecenia. Oprócz możliwości wykonania żądanego polecenia, Akcja może mieć powiązaną ikonę, skrót klawiaturowy, tekst podpowiedzi i tak dalej. Przycisk paska narzędzi lub element menu można całkowicie zainicjować tylko przy użyciu obiektu Action .
Nagrywanie makro
Jeśli wszystkie działania użytkownika są reprezentowane przez obiekty poleceń, program może rejestrować sekwencję działań, po prostu przechowując listę obiektów poleceń podczas ich wykonywania. Może następnie „odtworzyć” te same czynności, wykonując ponownie te same obiekty poleceń. Jeśli program osadza mechanizm skryptów, każdy obiekt polecenia może implementować metodę toScript() , a działania użytkownika można łatwo rejestrować jako skrypty.
Kod mobilny
Używając języków takich jak Java, w których kod może być przesyłany strumieniowo/wysurzany z jednej lokalizacji do drugiej za pośrednictwem programów URLClassloaders i Codebases, polecenia mogą umożliwić dostarczanie nowych zachowań do zdalnych lokalizacji (EJB Command, Master Worker).
Wielopoziomowe cofanie
Jeśli wszystkie działania użytkownika w programie są zaimplementowane jako obiekty poleceń, program może przechowywać stos ostatnio wykonanych poleceń. Gdy użytkownik chce cofnąć polecenie, program po prostu pobiera najnowszy obiekt polecenia i wykonuje swoją metodę undo () .
Sieć
Możliwe jest wysyłanie całych obiektów poleceń przez sieć, które mają być wykonywane na innych maszynach, na przykład akcje graczy w grach komputerowych.
Przetwarzanie równoległe
Gdzie polecenia są zapisywane jako zadania do współdzielonego zasobu i wykonywane równolegle przez wiele wątków (prawdopodobnie na zdalnych maszynach; ten wariant jest często określany jako wzorzec Master/Worker)
Paski postępu
Załóżmy, że program ma sekwencję poleceń, które wykonuje w określonej kolejności. Jeśli każdy obiekt polecenia ma metodę getEstimatedDuration() , program może łatwo oszacować całkowity czas trwania. Może pokazywać pasek postępu, który w znaczący sposób pokazuje, jak blisko ukończenia wszystkich zadań jest program.
Pule wątków
Typowa klasa puli wątków ogólnego przeznaczenia może mieć publiczną metodę addTask() , która dodaje element pracy do wewnętrznej kolejki zadań oczekujących na wykonanie. Utrzymuje pulę wątków, które wykonują polecenia z kolejki. Pozycje w kolejce to obiekty poleceń. Zazwyczaj te obiekty implementują wspólny interfejs, taki jak java.lang.Runnable, który pozwala puli wątków na wykonanie polecenia, nawet jeśli sama klasa puli wątków została napisana bez wiedzy o konkretnych zadaniach, do których byłaby używana.
Zachowanie transakcyjne
Podobnie jak w przypadku cofania, silnik bazy danych lub instalator oprogramowania może przechowywać listę operacji, które zostały lub zostaną wykonane. Jeśli jeden z nich zawiedzie, wszystkie inne można odwrócić lub odrzucić (zwykle nazywane wycofaniem ). Na przykład, jeśli dwie tabele bazy danych, które odwołują się do siebie, muszą zostać zaktualizowane, a druga aktualizacja nie powiedzie się, transakcja może zostać wycofana, tak aby pierwsza tabela nie zawierała teraz nieprawidłowego odwołania.
Czarodzieje
Często kreator przedstawia kilka stron konfiguracji dla pojedynczej akcji, która ma miejsce tylko wtedy, gdy użytkownik kliknie przycisk „Zakończ” na ostatniej stronie. W takich przypadkach naturalnym sposobem oddzielenia kodu interfejsu użytkownika od kodu aplikacji jest zaimplementowanie kreatora za pomocą obiektu polecenia. Obiekt polecenia jest tworzony przy pierwszym wyświetleniu kreatora. Każda strona kreatora przechowuje zmiany GUI w obiekcie polecenia, więc obiekt jest wypełniany w miarę postępów użytkownika. "Zakończ" po prostu wyzwala wywołanie execute() . W ten sposób klasa poleceń będzie działać.

Terminologia

Terminologia używana do opisywania implementacji wzorców poleceń nie jest spójna i dlatego może być myląca. Wynika to z niejednoznaczności , użycia synonimów i implementacji, które mogą zaciemniać oryginalny wzorzec, znacznie wykraczając poza niego.

  1. Dwuznaczność.
    1. Termin polecenie jest niejednoznaczny. Na przykład idź w górę, idź w górę może odnosić się do pojedynczego polecenia (idź w górę), które powinno zostać wykonane dwukrotnie, lub może odnosić się do dwóch poleceń, z których każde wykonuje to samo (w górę). Jeśli poprzednie polecenie zostanie dodane dwukrotnie do stosu cofania, oba elementy na stosie odnoszą się do tej samej instancji polecenia. Może to być właściwe, gdy polecenie można zawsze cofnąć w ten sam sposób (np. Przesuń w dół). Zarówno Gang of Four, jak i poniższy przykład Java używają tej interpretacji terminu polecenie . Z drugiej strony, jeśli te ostatnie polecenia zostaną dodane do stosu cofania, stos odnosi się do dwóch oddzielnych obiektów. Może to być odpowiednie, gdy każdy obiekt na stosie musi zawierać informacje umożliwiające cofnięcie polecenia. Na przykład, aby cofnąć polecenie usunięcia zaznaczenia , obiekt może zawierać kopię usuniętego tekstu, aby można go było ponownie wstawić, jeśli polecenie usunięcia zaznaczenia musi zostać cofnięte. Zauważ, że użycie oddzielnego obiektu dla każdego wywołania polecenia jest również przykładem wzorca łańcucha odpowiedzialności .
    2. Termin „ wykonaj” jest również niejednoznaczny. Może odnosić się do uruchomienia kodu zidentyfikowanego przez metodę execute obiektu polecenia . Jednak w Microsoft Windows Presentation Foundation polecenie jest uważane za wykonane po wywołaniu metody execute , ale nie musi to oznaczać, że kod aplikacji został uruchomiony. Dzieje się tak dopiero po dalszym przetwarzaniu zdarzeń.
  2. Synonimy i homonimy .
    1. Klient, źródło, wywołujący : przycisk, przycisk paska narzędzi lub element menu kliknięty, klawisz skrótu naciśnięty przez użytkownika.
    2. Command Object, Routed Command Object, Action Object : obiekt typu singleton (np. istnieje tylko jeden obiekt CopyCommand), który wie o klawiszach skrótów, obrazach przycisków, tekście polecenia itp. związanych z poleceniem. Obiekt źródłowy/wywołujący wywołuje metodę execute/performAction obiektu Command/Action. Obiekt Command/Action powiadamia odpowiednie obiekty źródłowe/wywołujące o zmianie dostępności polecenia/akcji. Dzięki temu przyciski i elementy menu stają się nieaktywne (wyszarzone), gdy nie można wykonać/wykonać polecenia/działania.
    3. Odbiornik, obiekt docelowy : obiekt, który ma zostać skopiowany, wklejony, przeniesiony itp. Obiekt odbiornika jest właścicielem metody, która jest wywoływana przez metodę wykonywania polecenia . Odbiorca jest zazwyczaj również obiektem docelowym. Na przykład, jeśli obiektem odbierającym jest kursor, a metoda nazywa się moveUp , można oczekiwać, że kursor jest celem akcji moveUp. Z drugiej strony, jeśli kod jest zdefiniowany przez sam obiekt polecenia, obiekt docelowy będzie całkowicie innym obiektem.
    4. Command Object, kierowane argumenty zdarzenia, obiekt zdarzenia : obiekt, który jest przekazywany ze źródła do obiektu Command/Action, do obiektu Target do kodu, który wykonuje pracę. Każde kliknięcie przycisku lub klawisz skrótu powoduje powstanie nowego obiektu polecenia/zdarzenia. Niektóre implementacje dodają więcej informacji do obiektu polecenia/zdarzenia, gdy są one przekazywane z jednego obiektu (np. CopyCommand) do innego (np. sekcja dokumentu). Inne implementacje umieszczają obiekty poleceń / zdarzeń w innych obiektach zdarzeń (takich jak pudełko w większym pudełku), gdy poruszają się wzdłuż linii, aby uniknąć konfliktów nazw. (Zobacz także wzorzec łańcucha odpowiedzialności .)
    5. Handler, ExecutedRoutedEventHandler, metoda, funkcja : rzeczywisty kod, który wykonuje kopiowanie, wklejanie, przenoszenie itp. W niektórych implementacjach kod obsługi jest częścią obiektu polecenia/działania. W innych implementacjach kod jest częścią odbiornika/obiektu docelowego, aw jeszcze innych implementacjach kod obsługi jest trzymany oddzielnie od innych obiektów.
    6. Command Manager, Undo Manager, Scheduler, Queue, Dispatcher, Invoker : obiekt, który umieszcza obiekty poleceń/zdarzeń na stosie cofania lub ponawiania lub zatrzymuje obiekty poleceń/zdarzeń, dopóki inne obiekty nie będą gotowe do działania na nich, lub który kieruje obiekty poleceń/zdarzeń do odpowiedniego obiektu odbiorcy/celu lub kodu obsługi.
  3. Implementacje, które znacznie wykraczają poza oryginalny wzorzec poleceń.
    1. Windows Presentation Foundation (WPF) firmy Microsoft wprowadza polecenia kierowane, które łączą wzorzec polecenia z przetwarzaniem zdarzeń. W rezultacie obiekt polecenia nie zawiera już odniesienia do obiektu docelowego ani odniesienia do kodu aplikacji. Zamiast tego wywołanie polecenia execute obiektu polecenia powoduje tak zwane wykonane zdarzenie kierowane, które podczas tunelowania lub propagacji zdarzenia może napotkać tak zwany obiekt powiązania, który identyfikuje cel i kod aplikacji, który jest wykonywany w tym momencie.

Przykład

Rozważ „prosty” przełącznik. W tym przykładzie konfigurujemy Switch za pomocą dwóch poleceń: włączyć światło i wyłączyć światło.

Zaletą tej konkretnej implementacji wzorca poleceń jest to, że przełącznik może być używany z dowolnym urządzeniem, nie tylko ze światłem. Switch w poniższej implementacji C# włącza i wyłącza światło, ale konstruktor Switcha jest w stanie zaakceptować dowolne podklasy Command dla swoich dwóch parametrów. Na przykład możesz skonfigurować Switch tak, aby uruchamiał silnik.

using System;

namespace CommandPattern
{    
    public interface ICommand
    {
        void Execute();
    }

    /* The Invoker class */
    public class Switch
    {
        ICommand _closedCommand;
        ICommand _openedCommand;

        public Switch(ICommand closedCommand, ICommand openedCommand)
        {
            this._closedCommand = closedCommand;
            this._openedCommand = openedCommand;
        }

        // Close the circuit / power on
        public void Close()
        {
           this._closedCommand.Execute();
        }

        // Open the circuit / power off
        public void Open()
        {
            this._openedCommand.Execute();
        }
    }

    /* An interface that defines actions that the receiver can perform */
    public interface ISwitchable
    {
        void PowerOn();
        void PowerOff();
    }

    /* The Receiver class */
    public class Light : ISwitchable
    {
        public void PowerOn()
        {
            Console.WriteLine("The light is on");
        }

        public void PowerOff()
        {
            Console.WriteLine("The light is off");
        }
    }

    /* The Command for turning on the device - ConcreteCommand #1 */
    public class CloseSwitchCommand : ICommand
    {
        private ISwitchable _switchable;

        public CloseSwitchCommand(ISwitchable switchable)
        {
            _switchable = switchable;
        }

        public void Execute()
        {
            _switchable.PowerOn();
        }
    }

    /* The Command for turning off the device - ConcreteCommand #2 */
    public class OpenSwitchCommand : ICommand
    {
        private ISwitchable _switchable;

        public OpenSwitchCommand(ISwitchable switchable)
        {
            _switchable = switchable;
        }

        public void Execute()
        {
            _switchable.PowerOff();
        }
    }

    /* The test class or client */
    internal class Program
    {
        public static void Main(string[] arguments)
        {
            string argument = arguments.Length > 0 ? arguments[0].ToUpper() : null;

            ISwitchable lamp = new Light();

            // Pass reference to the lamp instance to each command
            ICommand switchClose = new CloseSwitchCommand(lamp);
            ICommand switchOpen = new OpenSwitchCommand(lamp);

            // Pass reference to instances of the Command objects to the switch
            Switch @switch = new Switch(switchClose, switchOpen);

            if (argument == "ON")
            {
                // Switch (the Invoker) will invoke Execute() on the command object.
                @switch.Close();
            }
            else if (argument == "OFF")
            {
                // Switch (the Invoker) will invoke the Execute() on the command object.
                @switch.Open();
            }
            else
            {
                Console.WriteLine("Argument \"ON\" or \"OFF\" is required.");
            }
        }
    }
}


Zobacz też

Źródła

Pierwsza opublikowana wzmianka o użyciu klasy Command do implementacji systemów interaktywnych wydaje się pochodzić z artykułu Henry'ego Liebermana z 1985 roku. Pierwszy opublikowany opis (wielopoziomowych) cofanie ponawiania mechanizm, za pomocą klasy Command z wykonania i cofania metody oraz listę historii, wydaje się być pierwszym (1988) edycja Bertrand Meyer książki „s Oprogramowanie obiektowe Budowa , rozdział 12.2.

Bibliografia

Linki zewnętrzne