Dziedziczenie klas ------------------------- Pojęcie **dziedziczenia klas** jest fundamentalne dla programowania obiektowego. Bez **dziedziczenia** nie wyszlibyśmy poza etap zaawansowanych struktur. Dziedziczenie umożliwia dodawanie nowej funkcjonalności do klas już napisanych. Nowe klasy mają to, co miała **klasa bazowa** plus coś jeszcze. Na przykład atom jest nie tylko wektorem w 3D, ma też swoją nazwę. Indeks w zasadzie nie jest konieczny dla atomu, ale programy do modelowania w zasadzie nie mogą się bez tego obyć: .. code-block:: python :linenos: class Atom(Vec3): def __init__(self, id, name, x = 0, y = 0, z = 0) : super.__init__(x,y,z) self.__id = id self.__name = name Dla porównania analogiczny kod w C++: .. code-block:: cpp :linenos: class Atom : public Vec3 { public: Atom(const int id, const std::string & name, float x, float y, float z ) : Vec3(x,y,z) { id_=id; name_=name; } Atom(const int id, const std::string & name) : Vec3(0,0,0) , name_(name), id(id) {} } Klasę ``Vec3`` nazwiemy **klasą bazową** albo **nadklasą**; klasę ``Atom`` zaś **klasą potomną** albo **podklasą**. Klasa `Atom` **dziedziczy po** klasie ``Vec3`` wszystkie jej publiczne metody i pola. Dzięki temu ``Atom`` ma współrzędne oraz umie policzyć długość swojego wektora (odziedziczył ``length()``). W linii 4 kodu w Pythonie następuje wywołanie konstruktora klasy bazowej. W tym momencie klasa Vec3, a zatem i klasa Atom, uzyskuje pola ``__x``,``__y`` oraz ``__z``. Instrukcja ta jest poprawna dla Pythona w wersji *3.x*. Skrypty napisane dla Pythona *2.x* powinny wywoływać konstruktor klasy bazowej jak poniżej: .. code-block:: python :linenos: class Atom(Vec3): def __init__(self, id, name, x = 0, y = 0, z = 0) : super(Vec3, self).__init__() self.__id = id self.__name = name **Zwiększanie funkcjonalności klasy bazowej** W powyższym przypadku klasa bazowa ``Vec3`` dostała nowe *ficzery*, co zrobiło z niej ``Atom``. Każdy atom jest wektorem, ale nie każdy wektor to atom. .. _klasa_Atom: Pełna wersja klasy ``Atom`` znajduje się poniżej: .. raw:: html
(rozwiń kod klasy Atom) .. literalinclude:: programy/Atom.py :language: python :linenos: .. raw:: html
Prosta symulacja - oscylator harmoniczny ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Dla przykładu napiszmy prosty skrypt, który modeluje oscylator harmoniczny w jednym wymiarze: *Na ciało o masie* **m** *działa siła* :math:`-k x`, *na wskutek której oscyluje ono wokół położenia* **x0**. Proces ten opisany jest równaniem ruchu: .. math:: m\ddot{x} = - kx Równanie to rozwiązuje numerycznie (całkuje) poniższy skrypt: .. literalinclude:: simulation-simple.py :language: python :linenos: Wersja obiektowa będzie dużo dłuższa, ale za to łatwiejsza w rozbudowie. Zastanówmy się najpierw, jakie elementy powinniśmy reprezentować w programie? .. glossary:: **system** reprezentuje tu modelowany obiekt - oscylator harmoniczny (klasa ``Harmonic1DSystem``) **solver** rozwiązuje równanie ruchu; tu akurat jest to klasa ``EulerIntegrator`` całkująca wg schematu Eulera **observer** odpowiada za wydruk wyników na ekran .. literalinclude:: simulation-obj.py :language: python :linenos: :lines: 1-33, 44-52, 78-81, 84, 85, 87-91 Zaletą obiektowości jest to, że można łatwo wymieniać poszczególne komponenty programu na inne, zmieniając w ten sposób jego zachowanie. Na przykład rolą ``ObserveToScreen`` jest drukowanie aktualnego położenia oscylatora na ekranie. Łatwo można zmienić wyniki produkowane przez symulację, zmieniając observer na inny, np taki, który obserwuje tylko zakres położenia (jego minimalną i maksymalną wartość) albo obserwer, który nagrywa wyniki do pliku. .. literalinclude:: simulation-obj.py :language: python :linenos: :lines: 35-76 Wszystkie obserwery dziedziczą po klasie bazowej ``Observer``, która definiuje ich **interfejs**, czyli publiczne metody które one udostępniają. Są to metody ``observe()``, która *dokonuje obserwacji*, oraz ``finalize()``, która *kończy obserwacje*. Ta ostatnia może zamykać plik, rysować wykres, itp. Wszystkie klasy muszą mieć ten sam interfejs, a więc obie w/w metody. Nie każdy obserwer potrzebuje metody ``finalize()``, dlatego klasa bazowa dostarcza domyślną implementację, która po prostu nic nie robi (instrukcja ``pass``). Tylko dzięki temu poniższa linia programu działa poprawnie: .. literalinclude:: simulation-obj.py :language: python :linenos: :lines: 90 Gdyby choć jeden z obiektów na liście nie miał metody ``finalize()``, program zrzuciłby wyjątek.