Podsumowanie

W części (Niezbyt) oczywiste elementy Pythona pojawił się przykład process_files.py, w którym de facto wykorzystano wzorzec Dyspozytor (Dispatch Pattern). W tym przypadku jednak kluczami słownika były napisy, wartościami zaś - funkcje! Czy obiekty rzeczywiście są takie przydatne? W pomiższym przykładzie

 1# ---------- The version with functions (no objects at all) ----------
 2def woof():
 3    print("woof")
 4
 5
 6def purr():
 7    print("purr")
 8
 9
10say_sth = {"dog": woof, "cat": purr}
11
12for animal in ["dog", "dog", "cat"]:
13   say_sth[animal]()
14
15
16# ---------- The version with  objects ----------
17
18class Woof:
19    def __call__(self):
20        print("woof, woof")
21
22
23class Purr:
24    def __call__(self):
25        print("purr, purr")
26
27
28say_sth = {"dog": Woof(), "cat": Purr()}
29
30for animal in ["dog", "dog", "cat", "dog"]:
31    say_sth[animal]()

wykorzystano obydwa podejścia. Zauważ, że samo wykorzystanie dyspozytora opartego na obiektach (linie 30-31) jest identyczne co w wariancie z funkcjami (linie 12-13). Pisanie klas wydaje się zbędnym wysiłkiem. A jak by można było rozszerzyć funkcjonalność dyspozytora, np. tak, aby zliczał ile razy odezwało się każde ze zwierząt? Dzięki wykorzystaniu obiektów rozwiązanie jest bardzo proste:

 1class Speak:
 2    def __init__(self):
 3        self.n = 0
 4
 5
 6class Woof(Speak):
 7    def __call__(self):
 8        self.n += 1
 9        print("woof, woof", self.n)
10
11
12class Purr(Speak):
13    def __call__(self):
14        self.n += 1
15        print("purr, purr", self.n)
16
17
18say_sth = {"dog": Woof(), "cat": Purr(), "retriever": Woof()}
19
20for animal in ["dog", "cat", "cat", "retriever"]:
21    say_sth[animal]()

Zauważ, że retriever też szczeka, ale jest liczony oddzielnie niż inne psy!

Uważny czytelnik może zauwazy, że problem zliczania, ile razy została wywołana każda z funkcji, można rozwiązać stosując dekoratory funkcji. W takim przypadku jednak trzeba by owe liczniki zapisać jako zmienne globalne. Programowanie obiektowe umożliwia nam schowanie ich we wnętrzu obiektów.

Lektura uzupełniająca

https://rszalski.github.io/magicmethods/

Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, ,,Wzorce projektowe. Elementy oprogramowania obiektowego wielokrotnego użytku’’

https://python-3-patterns-idioms-test.readthedocs.io/en/latest/PythonDecorators.html

Spis pojęć

bazowa klasa

klasa po której inna klasa dziedziczy

chroniona składowa

pole bądź metoda klasy, widoczna tylko dla klas potomnych

delegacja

wzorzec projektowy

destruktor

metoda wywoływana automatycznie (i dokładnie raz) w momencie niszczenia obiektu; w Pythonie destruktor deklarujemy jako __del__():

def __del__(self) :
  # Zrob coś na samym koncu, np zamknij plik
enkapsulacja

ukrywanie implementacji; zapewnia ona, że obiekt nie może zmienić nieoczekiwanie stanu innych obiektów. Stan obiektu mogą zmienić jedynie jego metody publiczne, czyli interfejs. Enkapsulacja ułatwia testowanie i pielęgnację (maintenance) kodu.

fabryka klas

wzorzec projektowy

getter

metoda udostępniająca prywatną składową klasy; w Pythonie można ją zadeklarować korzystając z dekoratora @property:

@property
def val(self) :
  return self.__val   # Zwraca prywatną składową ``__val``
interfejs

zbiór metod publicznych klasy

iterable

obiekt jest iterable, jeżeli umie zwrócić iterator do swojej zawartości; w praktyce jest to coś, co można wpisać w pętlę for:

for l in coś : pass

Aby klasa w Pythonie była iterable, musi implementować magiczną metodę __iter__()

iterator

obiekt, który umie zwrócić następny element w sekwencji (posiada metodę __next__())

klasa

definicja obiektu; definiuje jego wszystkie pola i metody

klasa abstrakcyjna

klasa, która posiada choć jedną metodę abstraktyjną; w Pythonie klasy takie muszą dziedziczyć po klasie bazowej ABC zdefiniowanej w module abc

konstruktor

metoda wywoływana automatycznie (i dokładnie raz) w momencie tworzenia obiektu; w Pythonie destruktor konstruktor jako __init__():

def __init__(self) :
  # Zainicjuj wszystkie składowe klasy
konstruktor domyślny

konstruktor, który nie przyjmuje żadnych parametrów; ewentualnie korzysta z domyślnych wartości

konstruktor kopiujący

konstruktor, który tworzy głęboką kopię obiektu; nowo utworzony obiekt jest identyczny z tym, który był argumentem konstruktora

magic functions

TBD

metoda

funkcja zdefiniowana jako składowa klasy

metoda abstrakcyjna

metoda, która została jedynie zadeklarowana, lecz nie zaimplementowana; metoda taka musi być zaimplementowana w klasie potomnej. W Pythonie metody takie deklarujemy korzystając z dekoratora @abstractmethod

nadklasa

dowolna klasa, po której dana klasa dziedziczy (bezpośrednio lub nie)

obiekt (instancja klasy)

struktura, która może zawierać w sobie funkcje (metody) oraz zmienne (pola)

operator wywołania

operator zapisywany jako nawiasy zwykłe, czyli (); w Pythonie dodajemy go do klasy implementując w niej magiczną metodę __call__()

podklasa

synonim do klasy potomnej

pole (atrybut) klasy

zmienna zadeklarowana wewnątrz klasy

polimorfizm

obiekty tego samego typu (klasy bazowej) zachowują się różnie

potomna klasa

klasa, która dziedziczy po jakiejś klasie (zwanej klasą bazową)

prywatna składowa

pole bądź metoda klasy, niewidoczna z zewnątrz klasy; jedynie składowe klasy mogą korzystać z prywatnych składowych klasy, do której należą

przeciążenie

klasa ma kilka metod o takiej samej nazwie lecz różnych argumentach; w Pythonie realizowane przez *args

przesłanianie

klasa potomna ma metodę o takiej samej nazwie i argumentach, co klasa bazowa, lecz o zmienionym zachowaniu

publiczna składowa

pole bądź metoda klasy widoczna dla wszystkich innych obiektów, metod i funkcji; w każdym miejscu kodu można odczytać bądź zmienić jej wartość

singleton

wzorzec projektowy w którym możliwe jest stworzenie tylko jednego obiektu danej klasy

statyczne składowe

składowe klasy, które nie są związane z żadnym konkretnym obiektem klasy; istnieją nawet wtedy, kiedy jeszcze nie stworzono żadnego obiektu tej klasy; mogą być prywatne lub publiczne