Komenda (Command Pattern)

Note

Komenda (Polecenie) to obiektowy odpowiednik wywołania funkcji

Komenda (zwana też poleceniem) to obiekt, który przechowuje w sobie dane potrzebne do wykonania swojego kodu (czyli komendy). Komendy - jak każde obiekty - można przechowywać w liście. Wzorzec ten umożliwia np. elastyczną zmianę działania programu w trakcie jego trwania.

Aby zademonstrować ten wzorzec (a także i kilka innych, na kolejnych stronach), napiszmy program, który przemieszcza żółwia w dwóch wymiarach. Zacznijmy od czegoś prostego: połozenie żółwia zapisane jest w globalnych zmiennych x i y. Może się on przemieszczać w jednym z czterech kierunków: do przodu, do tyłu, w prawo i w lewo, ruchy te są realizowane odpowiednio przez funkcje f(), b(), r() i l():

 1x, y, = 0, 0                # initial position of a turtle
 2
 3def r():
 4    global x, y
 5    x +=1     # go right, turtle!
 6
 7def l():
 8    global x, y
 9    x -=1     # go left, turtle!
10
11def f():
12    global x, y
13    y +=1     # go forward, turtle!
14
15def b():
16    global x, y
17    y -=1     # go back, turtle!
18
19user_typed_keys = "RRFLB"
20for key in user_typed_keys:
21    if key == 'R': r()
22    elif key == 'L': l()
23    elif key == 'F': f()
24    elif key == 'B': b()
25    else: print("unused key:", key)
26    print("current position:", x, y)

Załóżmy, że użytkownik wcisnął sekwencję klawiszy jak zdefiniowano w zmiennej user_typed_keys (linia 19), a żółw poruszył się stosownie do komend. Program działa poprawnie, jest to jednak podejście mało rozwojowe.

Obiekt komenda

Stwórzmy oddzielny obiekt dla każdej możliwości ruchu żółwia:

 1x, y, = 0, 0                # initial position of a turtle
 2
 3class R:
 4    def __call__(self):
 5        global x, y
 6        x += 1     # go right, turtle!
 7
 8class L:
 9    def __call__(self):
10        global x, y
11        x -= 1     # go left, turtle!
12
13class F:
14    def __call__(self):
15        global x, y
16        y += 1     # go forward, turtle!
17
18class B:
19    def __call__(self):
20        global x, y
21        y -= 1     # go back, turtle!
22
23class R2:
24    def __call__(self):
25        global x, y
26        x += 2     # go right faster, turtle!
27
28
29user_typed_keys = "RRFLB"
30r = R()
31l = L()
32b = B()
33f = F()
34r = R2()                        # Actually, let's go right faster
35for key in user_typed_keys:
36    if key == 'R': r()
37    elif key == 'L': l()
38    elif key == 'F': f()
39    elif key == 'B': b()
40    else: print("unused key:", key)
41    print("current position:", x, y)
42

Na pierwszy rzut oka wydaje się, że jedyne, co osiągnęliśmy, to wydłużenie programu, który robi to samo, co poprzedni. Zauważ jednak, że w linii 34 podmieniamy komendę ruchu w prawo na inną: r = R2(), dzięki czemu żółw w prawo porusza się dwa razy szybciej niż w innych kierunkach.

Obiekty mogą coś, czego nie mogą funkcje: przechowywać dane. Poniżej przedstawiam drugie podejście do komend żółwia; tym razem każda komenda wie, o ile kroków ma się ruszyć żółw. Tym samym klasa R2 przestała być potrzebna. Ponieważ każda komenda posiada tą nową funkcjonalność, została ona dodana do klasy bazowej TurtleCommand, po której dziedziczą wszystkie komendy.

 1x, y, = 0, 0                # initial position of a turtle
 2
 3class TurtleCommand:
 4
 5    def __init__(self, n_steps=1):
 6        """Base class for a turtle command"""
 7        self.__n_steps = int(n_steps)           # just to be sure it's not a string
 8
 9    @property
10    def n_steps(self): return self.__n_steps
11
12class R(TurtleCommand):
13    def __init__(self, n_steps):
14        super().__init__(n_steps)
15
16    def __call__(self):
17        global x, y
18        x += self.n_steps     # go right, turtle!
19
20class L(TurtleCommand):
21    def __init__(self, n_steps):
22        super().__init__(n_steps)
23
24    def __call__(self):
25        global x, y
26        x -= self.n_steps     # go left, turtle!
27
28class F(TurtleCommand):
29    def __init__(self, n_steps):
30        super().__init__(n_steps)
31
32    def __call__(self):
33        global x, y
34        y += self.n_steps     # go forward, turtle!
35
36class B(TurtleCommand):
37    def __init__(self, n_steps):
38        super().__init__(n_steps)
39
40    def __call__(self):
41        global x, y
42        y -= self.n_steps     # go back, turtle!
43
44
45rectangle = [R(3), F(2), L(3), B(2)]
46square = [R(2), F(2), L(2), B(2)]
47for cmd in rectangle:
48    cmd()
49    print("current position:", x, y)
50

Zauważ dodatkowo, że komendy-obiekty można łatwo przechowywać w listach.