1/36
Fundamenty projektowania

Wykład 7: Klasy Abstrakcyjne

Jak pisać kod, który wymusza porządek? Czym są "szablony" dla innych klas? Dzisiaj zagłębimy się w jeden z najważniejszych filarów programowania obiektowego.

  • Abstrakcja: Ukrywanie szczegółów implementacji.
  • Szablony: Tworzenie wzorców dla całych rodzin obiektów.
  • Moduł ABC: Standardowe narzędzie Pythona do abstrakcji.
  • Wymuszanie metod: Jak upewnić się, że programista o czymś nie zapomniał.
  • Interfejsy: Kontrakty, których musi przestrzegać kod.
  • Praktyczne wzorce: Kiedy klasa staje się abstrakcyjna.
2/36
Czym jest Abstrakcja?

Esencja obiektu

Abstrakcja to proces odrzucania mało istotnych cech obiektu, aby skupić się na tych, które są kluczowe dla danego problemu.

W programowaniu oznacza to definicję co obiekt ma robić, ale niekoniecznie jak ma to robić w każdym szczególe.

  • Przykład: "Pojazd" to pojęcie abstrakcyjne. Wiemy, że ma silnik i się porusza, ale nie wiemy, czy to motor, czy ciężarówka.
  • Cel: Ułatwienie zarządzania złożonością poprzez operowanie na ogólnych pojęciach.
3/36
Problem: Niepełne szablony

Kiedy samo dziedziczenie nie wystarcza?

Wyobraźmy sobie klasę bazową Figura. Każda figura powinna mieć metodę pole().

class Figura:
    def oblicz_pole(self):
        pass # Co tu wpisać? Trójkąt i koło liczy się inaczej!

Problem pojawia się, gdy ktoś utworzy obiekt samej klasy Figura lub zapomni nadpisać metodę w klasie pochodnej. Program może działać błędnie, zamiast "wykrzyczeć" błąd projektowy.

4/36
Wprowadzenie do Klasy Abstrakcyjnej

Definicja i rola

Klasa Abstrakcyjna to klasa, która:

  • Nie może posiadać własnych instancji (obiektów).
  • Służy wyłącznie jako wzorzec dla innych klas.
  • Może definiować metody "puste" (abstrakcyjne), które muszą zostać zaimplementowane w klasach potomnych.

W Pythonie do tworzenia takich klas używamy modułu abc (Abstract Base Classes).

5/36
Moduł abc

Narzędzia pracy

Aby stworzyć klasę abstrakcyjną, musimy zaimportować dwa kluczowe elementy:

  • ABC: Klasa bazowa, po której dziedziczymy.
  • abstractmethod: Dekorator, którym oznaczamy metody wymagane.
from abc import ABC, abstractmethod

class MojaAbstrakcja(ABC):
    @abstractmethod
    def metoda_obowiazkowa(self):
        pass
6/36
Pierwszy przykład: Zwierzęta

Szablon dla gatunków

from abc import ABC, abstractmethod

class Zwierzę(ABC):
    @abstractmethod
    def wydaj_dzwiek(self):
        pass

class Pies(Zwierzę):
    def wydaj_dzwiek(self):
        print("Hau hau!")

class Kot(Zwierzę):
    def wydaj_dzwiek(self):
        print("Miau!")
7/36
Blokada instancjonowania

Ochrona przed błędem

Próba stworzenia obiektu klasy, która dziedziczy po ABC i ma metody abstrakcyjne, zakończy się błędem TypeError.

z = Zwierzę()
# TypeError: Can't instantiate abstract class Zwierzę 
# with abstract method wydaj_dzwiek

Dzięki temu mamy pewność, że w systemie nigdy nie pojawi się "byt niedokończony", który nie wie, jak się zachować.

8/36
Zapominalski programista

Wymuszanie implementacji

Co jeśli odziedziczymy po Zwierzę, ale zapomnimy napisać wydaj_dzwiek()?

class Chomik(Zwierzę):
    pass # Zapomnieliśmy o metodzie!

c = Chomik()
# BŁĄD! Chomik też jest teraz uznany za klasę abstrakcyjną.

Python pilnuje, aby każda podklasa "wypełniła kontrakt" podpisany w klasie bazowej.

9/36
Abstrakcyjne Właściwości

Wymuszanie atrybutów

Możemy wymusić, aby klasa potomna posiadała konkretne property (getter/setter).

class Pojazd(ABC):
    @property
    @abstractmethod
    def prędkość_max(self):
        pass

class Auto(Pojazd):
    @property
    def prędkość_max(self):
        return 200
10/36
Gdy klasa bazowa ma kod

Metody konkretne w klasach ABC

Klasa abstrakcyjna może zawierać normalne, działające metody, które będą współdzielone przez wszystkie dzieci.

class Pracownik(ABC):
    def meldunek(self):
        print("Jestem w pracy.")

    @abstractmethod
    def pracuj(self):
        pass

To świetne miejsce na kod, który jest identyczny dla każdego gatunku/typu (np. logowanie do bazy).

11/36
Kod w metodzie abstrakcyjnej?

Dziwna, ale użyteczna cecha

Choć brzmi to nielogicznie, metoda oznaczona @abstractmethod może posiadać treść. Możemy ją wywołać z klasy potomnej za pomocą super().

class Baza(ABC):
    @abstractmethod
    def start(self):
        print("Przygotowanie systemu...")

class Apka(Baza):
    def start(self):
        super().start() # Wywołanie kodu z ABC
        print("Uruchamiam interfejs.")
12/36
Wyzwanie: System Płatności

Analiza przypadku

Projektujemy system obsługujący różne metody płatności. Każda z nich działa inaczej, ale każda musi kończyć się efektem "zapłacono".

Wymagania dla każdej płatności:

  • Połącz się z bramką bankową.
  • Prześlij kwotę i walutę.
  • Odbierz potwierdzenie.
  • Zapisz log w naszej bazie (wspólne dla wszystkich).
13/36
Implementacja Systemu Płatności (Kod)

Architektura rozwiązania

class Płatność(ABC):
    def loguj_transakcje(self, kwota):
        print(f"Log: Próba zapłaty {kwota} PLN")

    @abstractmethod
    def autoryzuj(self): pass

    @abstractmethod
    def wykonaj_przelew(self, kwota): pass
14/36
Podklasy Systemu Płatności

Różne silniki, ten sam interfejs

class KartaKredytowa(Płatność):
    def autoryzuj(self):
        print("Sprawdzam limit karty...")
    
    def wykonaj_przelew(self, kwota):
        print(f"Karta obciążona o {kwota}")

class Kryptowaluty(Płatność):
    def autoryzuj(self):
        print("Weryfikuję blockchain...")
    
    def wykonaj_przelew(self, kwota):
        print(f"Wysłano {kwota} jednostek krypto")
15/36
Czym jest Interfejs?

Kontrakt techniczny

W wielu językach (Java, C#) istnieje słowo kluczowe interface. Python go nie ma, ale klasy czysto abstrakcyjne (tylko metody bez kodu) pełnią tę samą rolę.

Interfejs mówi: "Nie obchodzi mnie, kim jesteś, ale musisz umieć wykonać te 5 operacji".

Klasa abstrakcyjna to "rodzic", który daje geny (kod). Interfejs to "umowa", która mówi, co klasa ma potrafić.
16/36
Różnice: Klasa Bazowa vs ABC

Kiedy wybrać którą?

Cecha Zwykła Klasa Bazowa Klasa Abstrakcyjna (ABC)
Obiekty Można tworzyć (np. p = Pojazd()) ZABLOKOWANE
Wymuszanie Brak (dziecko może nic nie robić) OBOWIĄZKOWE nadpisywanie
Zastosowanie Gdy pojęcie bazowe ma sens samo w sobie Gdy baza to tylko czysty koncept
17/36
Hierarchia obiektów w ABC

Wpływ na sprawdzanie typów

Funkcje isinstance() i issubclass() działają z klasami abstrakcyjnymi bez problemu.

p = Pies()
isinstance(p, Zwierzę) # True
issubclass(Kot, Zwierzę) # True

Dzięki temu nasz program może traktować wszystkie zwierzęta tak samo, mimo że każdy gatunek ma inną implementację dźwięku.

18/36
Wirtualne podklasy: register()

Luźne powiązanie

Możemy powiedzieć Pythonowi, że jakaś klasa jest "wirtualnym dzieckiem" ABC, nawet jeśli po niej nie dziedziczy. Używamy do tego metody .register().

class ObcaKlasa:
    pass

Zwierzę.register(ObcaKlasa)

o = ObcaKlasa()
print(isinstance(o, Zwierzę)) # True!

Uwaga: To nie wymusi na ObcaKlasa implementacji metod. To tylko informacja dla systemu typów.

19/36
Przetwarzanie dokumentów

Inny przykład praktyczny

Wyobraźmy sobie aplikację do konwersji plików. Rodzaje: PDF, TXT, DOCX. Każdy czyta się inaczej.

class Dokument(ABC):
    @abstractmethod
    def wczytaj_tekst(self, ścieżka): pass

class DokumentPDF(Dokument):
    def wczytaj_tekst(self, ścieżka):
        # skomplikowana logika PDF
        return "Tekst z PDF"
20/36
Dlaczego ABC jest bezpieczniejsze?

Fail Fast (Szybka awaria)

W programowaniu "szybka awaria" to zaleta. Klasy abstrakcyjne powodują, że błąd projektowy (brak metody) ujawnia się już w momencie próby stworzenia obiektu, a nie godzinę później w środku skomplikowanych obliczeń.

  • Bez ABC: Błąd AttributeError przy wywołaniu metody.
  • Z ABC: Błąd TypeError przy tworzeniu obiektu.
21/36
Klasa abstrakcyjna a Metaklasy

Dla ciekawskich

Technicznie ABC korzysta z mechanizmu metaklas w Pythonie. Metaklasa to "klasa klasy".

W starszych wersjach Pythona (przed 3.4) pisało się:

# Stary sposób
class Baza(metaclass=ABCMeta):
    pass

Nowy sposób class Baza(ABC) jest po prostu skrótem, który czyni kod czytelniejszym.

22/36
Częsty błąd: Pomylenie ABC z Polimorfizmem

Niewłaściwe użycie

Nie każda klasa bazowa musi być abstrakcyjna. Jeśli planujesz tworzyć obiekty klasy bazowej (np. klasa Uzytkownik, a potem Admin), nie używaj ABC.

Zasada praktyczna: Czy pojęcie, które opisujesz, istnieje fizycznie? Jeśli nie (np. "Kształt", "Transakcja", "Pracownik"), użyj klasy abstrakcyjnej.

23/36
Abstrakcja w bibliotekach Pythona

Gdzie to spotkasz?

Biblioteka standardowa Pythona jest pełna klas abstrakcyjnych. Na przykład moduł collections.abc definiuje standardy dla:

  • Iterable: Wszystko, po czym można iterować pętlą for.
  • Sequence: Listy, krotki, napisy (mają index i długość).
  • Mapping: Słowniki (mają klucze i wartości).

Jeśli piszesz własny typ kolekcji, powinieneś dziedziczyć po odpowiednim ABC.

24/36
Wielodziedziczenie i ABC

Mieszanie kontraktów

Klasa może dziedziczyć po wielu klasach abstrakcyjnych jednocześnie. Wtedy musi zaimplementować metody z wszystkich z nich.

class Robot(LatajacyABC, JezdzacyABC):
    def startuj(self): pass
    def jedz(self): pass

To pozwala na składanie obiektów z gotowych "umiejętności" (znane jako wzorzec Mixin).

25/36
Czy @abstractmethod musi być u góry?

Kolejność dekoratorów

To bardzo ważne! @abstractmethod powinien być zazwyczaj dekoratorem najbardziej wewnętrznym (najbliżej definicji def).

@staticmethod
@abstractmethod
def funkcja(): pass # POPRAWNIE

W przypadku @property kolejność jest taka sama - @property musi być najbardziej zewnętrzny (najbliżej nazwy klasy), a @abstractmethod pod nim. Dzięki temu właściwość zachowuje swoje działanie jako getter.

26/36
Przykład: System graficzny

Rysowanie na ekranie

Każdy element GUI (przycisk, suwak, pole tekstowe) musi potrafić się "narysować".

class Komponent(ABC):
    @abstractmethod
    def rysuj(self, ekran): pass

Główna pętla programu może po prostu wywołać obiekt.rysuj() na liście wszystkich komponentów, nie wiedząc, czym one dokładnie są.

27/36
Projektowanie "od góry"

Top-down Design

Używając klas abstrakcyjnych, możesz zaprojektować cały system bez napisania ani jednej linii logiki. Definiujesz tylko nazwy metod i ich argumenty.

Póżniej Ty lub Twój zespół możecie "wypełniać" te szablony treścią.

To kluczowe przy pracy w dużych zespołach programistycznych.
28/36
Słowo o enkapsulacji w ABC

Prywatność w abstrakcji

Mimo że klasa jest abstrakcyjna, może posiadać pola prywatne (__pole), które będą dostępne dla metod zdefiniowanych wewnątrz ABC.

class Silnik(ABC):
    def __init__(self):
        self.__status = "Stop"
29/36
Podsumowanie: Zalety ABC

Dlaczego warto?

  1. Jasność: Od razu widać, że klasa jest wzorcem, a nie gotowym produktem.
  2. Bezpieczeństwo: Brak możliwości stworzenia błędnego, pustego obiektu.
  3. Wymuszanie: Chroni przed zapominaniem o kluczowych funkcjonalnościach.
  4. Standaryzacja: Wszystkie klasy pochodne wyglądają i zachowują się podobnie.
30/36
Kiedy NIE używać ABC?

Nadmiarowość to błąd

Nie twórz klasy abstrakcyjnej, jeśli masz tylko jedną klasę potomną i nie planujesz więcej. To niepotrzebne skomplikowanie kodu (tzw. Overengineering).

Zasada: Jeśli klasa bazowa może działać samodzielnie, zrób z niej zwykłą klasę bazową.

31/36
Abstrakcyjne Metody Statyczne

Wspólne narzędzia

Możesz wymusić, aby klasa miała metodę statyczną lub klasową.

class Fabryka(ABC):
    @classmethod
    @abstractmethod
    def stworz(cls): pass
32/36
Sprawdź swoją wiedzę #1

Pytanie kontrolne

Czy klasa abstrakcyjna może posiadać konstruktor __init__?

  • TAK: Może on inicjować pola, które będą wspólne dla wszystkich klas dzieci (używamy wtedy super().__init__).
  • Pamiętaj jednak, że samo wywołanie Baza() nadal wyrzuci błąd, jeśli są w niej metody abstrakcyjne.
33/36
Sprawdź swoją wiedzę #2

Pytanie kontrolne

Czy podklasa klasy abstrakcyjnej też może być abstrakcyjna?

  • TAK: Jeśli podklasa nie zaimplementuje wszystkich metod z rodzica, ona również staje się abstrakcyjna i nie można tworzyć jej obiektów.
  • Jest to przydatne przy tworzeniu wielopoziomowych hierarchii (np. Zwierze -> Ssaki -> Pies).
34/36
Praktyka: Zadanie domowe

Mały projekt

Zaprojektuj system dla urządzeń inteligentnego domu (IoT). Stwórz klasę abstrakcyjną Urzadzenie z metodami wlacz() i wylacz().

Następnie stwórz dwie konkretne klasy:

  • Żarówka (wypisuje "Świecę").
  • Termostat (wypisuje "Grzeję").

Spróbuj utworzyć listę urządzeń i przełączyć je wszystkie jedną pętlą.

35/36
Podsumowanie wykładu

Kluczowe pojęcia

  • Moduł ABC: Fundament abstrakcji w Pythonie.
  • @abstractmethod: Strażnik implementacji.
  • Wzorzec-Szablon: Główna rola klas abstrakcyjnych.
  • Projektowanie: Najpierw definicja ogólna, potem szczegóły.

W następnym wykładzie: Iteratory i Generatory – jak elegancko przetwarzać duże zbiory danych!

36/36
Koniec Wykładu 7

Dziękuję za uwagę!

Zapraszam do zadawania pytań i eksperymentowania z kodem.

Materiały: Programowanie Strukturalne i Obiektowe | Informatyka I Rok