Cześć, w poprzednim artykule omówiliśmy podstawy programowania obiektowego w Pythonie. Nadszedł teraz czas, aby poznaną teorię przekuć w praktykę. W ramach tego artykułu poznasz jeden z najważniejszych procesów związanych z programowaniem, a mianowicie proces refaktoryzacji. Dokonamy również zmian w aplikacji kalkulatora, którą napisaliśmy w artykule Pierwsze koty za płoty – Python – pierwsza aplikacja. Celem tych zmian będzie refaktoryzacja skryptu, tak aby działał z wykorzystaniem klas i obiektów. Zatem do dzieła!
Refaktoryzacja - co to takiego?
Refaktoryzacja to jeden z najważniejszych procesów w programowaniu. Jest to nieustająca walka o jak najlepszą jakość, wydajność oraz czytelność kodu. Refaktoryzacja powinna być wykonywana systematycznie oraz na bieżąco. Jest to proces szczególnie istotny w oprogramowaniu, które jest zaniedbane lub ma już za sobą swoje lata świetności. Jest ona również bardzo ważna w projektach, w których wdrażanie nowych funkcji/mechanizmów jest bardzo trudne. Dobrze przeprowadzona refaktoryzkcja skutkuje ogólną poprawą jakości kodu.
Refaktoryzacja często jest wynikiem zaciągnięcia tzw. długu technicznego, czyli wybrania pozornie łatwiejszej/szybciej ścieżki do osiągnięcia jakiegoś celu, która w dłuższej perspektywie staje się mniej opłacalna. jeżeli chciałbyś dowiedzieć się więcej na temat długu technicznego polecam zapoznać się z tym artykułem: Dług techniczny – co to jest (przyczyny i przykłady)
Rafaktoryzacja skryptu do modelu obiektowego
Jak wspomniałem wcześniej w ramach tego artykułu zajmiemy się refaktoryzacją skryptu utworzonego w jednym z poprzednich artykułów, a mianowicie skryptu kalkulatora:
Klasa Calculator – wyodrębnienie operacji matematycznych do osobnej jednostki
Jak możesz zauważyć powyższa klasa implementuje wszystkie operacje, które wcześniej były zaimplementowane w skrypcie. Elementem, na który warto zwrócić więcej uwagę jest metoda divide, ponieważ oprócz wykonania operacji matematycznej na zmiennych, sprawdza czy czasem dzielnik nie jest równy 0. W przypadku kiedy, jego wartość jest równa 0 to rzuca wyjątek, w wyniku którego wykonywanie tej operacji zostanie przerwane. W związku z tym możesz być pewny, iż każde wywołanie tej metody sprawdzi wartość dzielnika i w razie wykrycia problemu zawsze zwróci wyjątek ZeroDivisionError.
Kod źródłowy powyższej klasy jest spójny oraz przejrzysty. Jest ona odpowiedzialna tylko i wyłącznie za dokonanie obliczeń matematycznych. Dzięki temu skrypt wykorzystujący klasę Calculator nie musi zawierać kodu związanego z konkretnymi obliczeniami. Wystarczy tylko, iż wywoła odpowiednią funkcję klasy Calculator. Ukrywanie skomplikowanej logiki w klasach jest bardzo użyteczne, ponieważ pozwala na współdzielenie tej samej logiki w różnych miejscach w kodzie bez konieczności kopiowania kodu.
Skoro udało Ci się już przenieść całą logikę obliczeń matematycznych do osobnej klasy, należy teraz posprzątać trochę główny skrypt. W tym celu zamienimy wszystkie operacje matematyczne na wywołania metod powyższej klasy. Poniżej znajduje się kod skryptu po zmianach:
Przyjrzymy się chwilę zmianom dokonanym powyżej. Pierwszym elementem na który warto zwrócić uwagę jest linia nr.1
from calculator import CalculatorZadaniem tej linii jest wskazanie interpreterowi, iż kod wykorzystuje klasę Calculator z pliku/pakietu calculator. Bez tej linii nie byłoby możliwości uruchomienia skryptu, ponieważ interpreter nie wiedziałby gdzie szukać informacji o tej klasie.
Console – wyodbrędnienie logiki obsługi wejścia/wyjścia
Kolejnym krokiem procesu refaktoryzacji skryptu, który warto byłoby wykonać jest zamiana wywołań metod print oraz input. Przeniesienie kodu związanego z obsługą mechanizmów wejścia/wyjścia (odczytu danych z konsoli i wypisania ich na ekranie), pozwoli na uniezależnienie głównego skryptu od konkretnych operacji związanych z konsolą. Dzięki temu kod skryptu stanie się mniej zależny od terminala, platformy czy też środowiska uruchomieniowego. Na początku skupmy się na utworzeniu klasy Console. Jej kod możesz zobaczyć poniżej:
Jak możesz zobaczyć jedynym zadaniem tej klasy jest ukrycie mechanizmu obsługi wiersza poleceń za metodami write_line, read_line, read_line_as_int.
Przejdźmy teraz do refaktoryzacji głównego skryptu, tak aby go uprościć i wykorzystać w nim nowo utworzoną klasę Console:
Zobacz, iż nie zawiera on żadnej konkretnej logiki obsługi operacji matematycznych czy pobierania/wypisywania informacji na ekranie. Zamiast tego do obsługi tych elementów wykorzystuje klasy, które są pewną warstwą abstrakcji. Dzięki takiemu podejściu i ukryciu większości logiki za klasami, w łatwy sposób możesz je wymieniać na inne mechanizmy. Przyjmijmy, iż chciałbyś zmienić logikę wyświetlania informacji tak, aby dodatkowo była zapisywana do pliku tesktowego.
ConsoleFile – pobieraj dane z konsoli zapisuj wyjście do pliku
W Pythonie dziedziczenie polega na dziedziczeniu metod i atrybutów z jednej klasy przez inną klasę, nazywaną klasą pochodną lub klasą potomną. Klasa pochodna może używać metod i atrybutów z klasy bazowej i implementować dodatkowe metody lub atrybuty, które są specyficzne dla danej klasy.
Aby stworzyć klasę pochodną, należy użyć składni: “class NazwaKlasyPochodnej(NazwaKlasyBazowej):“. Metoda __init__ klasy pochodnej może wywołać metodę __init__ klasy bazowej dzięki funkcji super(), co umożliwia dziedziczenie konstruktora klasy bazowej.
W powyższym kodzie możesz zobaczyć, iż klasa ConsoleFile dziedziczy po klasie Console. Klasa pochodna rozszerza konstruktor klasy bazowej oraz wymaga podania ścieżki do pliku, w którym mają być zapisywane informacje. Kolejnym ważnym aspektem jest rozszerzenie metody write_line o kod związany z zapisywaniem tej samej wiadomości do pliku.
Klasa ConsoleFile w linii 7 otwiera plik o przekazanej nazwie wraz z prawami do zapisu (jeśli plik istnieje powinien zostać nadpisany. Świadczy o tym przekazanie parametru ‘w’ jako drugiego argumentu funkcji open. Wykorzystuje do tego wbudowaną w Pythona metodę open. Otwiera/tworzy ona nowy zasób systemowy w postaci pliku, który powinien zostać zamknięty, gdy tylko nie będzie już potrzebny, w celu zwolnienia zasobów systemowych. Służy do tego metoda close() wywoływana w linii 15.
Finalna wersja skryptu
Skoro posiadasz już klasę, która potrafi wypisywać informację do pliku oraz na ekranie konsoli należy teraz dokonać zmiany w głównym skrypcie tak aby korzystał z nowej klasy:
W celu wykorzystania nowego mechanizmu obsługi wejśca/wyjścia w głównym skrypcie należy zaimportować poprzednio utworzoną klasę ConsoleFile oraz utworzyć jej instancję przekazując nazwę pliku, do którego powinna zapisywać informacje. Dodatkowo, ze względu na to, iż nowa klasa korzysta z plików musimy pamiętać o zwolnieniu zasobów przy zamykaniu aplikacji. W tym celu należy dodać wywołanie metody close w linii 30 skryptu.
Efektem uruchomienia skryptu powinno być utworzenie nowego pliku o przykładowej zawartości:
Powyżej znajduje się finalna wersja skryptu. W ramach tego artykułu miałeś okazję lepiej zapoznać się z programowaniem obiektowym. Przeprowadziłem Ciebie również, krok po kroku, przez proces refaktoryzacji, w wyniku którego skrypt stał się czytelniejszy oraz zaczął działać na warstwie abstrakcji.
Mam nadzieję, iż lektura była interesującą. Kod kalkulatora jest dostępny na moim GitHubie pod adresem: https://github.com/devkotpl/python-calculator