02 - Elementy programowania obiektowego
Podstawy przetwarzania danych
Politechnika Poznańska, Instytut Robotyki i Inteligencji Maszynowej
Ćwiczenie laboratoryjne 2: elementy programowania obiektowego
Powrót do spisu treści ćwiczeń laboratoryjnych
Programowanie obiektowe
Programowanie obiektowe (ang. object-oriented programming, OOP) – paradygmat programowania, w którym programy definiuje się za pomocą obiektów – elementów łączących stan (czyli dane, nazywane najczęściej atrybutami) i zachowanie (czyli procedury, tu: metody). Obiektowy program komputerowy wyrażony jest jako zbiór takich obiektów, komunikujących się pomiędzy sobą w celu wykonywania zadań.
Podejście to różni się od tradycyjnego programowania proceduralnego, gdzie dane i procedury nie są ze sobą bezpośrednio związane. Programowanie obiektowe ma ułatwić pisanie, konserwację i wielokrotne użycie programów lub ich fragmentów. [źródło]
Klasy i obiekty
Python jest językiem programowania wspierającym paradygmat programowania obiektowego. W Pythonie wszystko jest obiektami: liczby, listy, słowniki itd. Klasy z kolei, są podstawowym elementem programowania obiektowego. Pozwalają na połączenie danych i operacji na tych danych w jedną całość. Dodanie własnej klasy pozwala na zdefiniowanie nowego typu danych, który będzie miał określone atrybuty i metody (metody są odpowiednikami funkcji w klasach).
Utworzenie klasy w języku Python jest bardzo proste. Wystarczy użyć słowa kluczowego class
i podać nazwę klasy. Wewnątrz klasy można zdefiniować atrybuty (zmienne) i metody (funkcje).
class Character:
= "John the Brave"
name = 100
health = ["sword", "shield"]
items
def introduce(self):
print("Hello!")
Powyższa klasa Character
posiada trzy atrybuty: name
, health
i items
oraz jedną metodę introduce
. Zwróć uwagę, że metoda introduce
przyjmuje argument self
. Argument self
jest referencją do obiektu, na którym wywołano metodę. Dzięki temu metoda może odwoływać się do atrybutów obiektu (więcej przykładów będzie niżej).
Aktualnie klasa Character
posiada trzy zmienne, które są wspólne dla wszystkich obiektów tej klasy, co nie jest pożądane. Dobrze jest ustawiać te zmienne w konstruktorze klasy. Konstruktor klasy to specjalna metoda __init__
, która jest wywoływana podczas tworzenia nowego obiektu. W konstruktorze można przekazać argumenty, które będą inicjalizować atrybuty obiektu.
class Character:
def __init__(self, name, health, items):
self.name = name
if health > 0:
self.health = health
else: # Postać nie może mieć mniej niż 0 punktów życia
self.health = 0
self.health = health
self.items = items
def introduce(self):
print("Hello!")
def is_alive(self):
return self.health > 0
Zauważ, że w konstruktorze zamiast name
, health
i items
używamy self.name
, self.health
i self.items
. Tak samo będziemy się odwoływać do tych atrybutów w pozostałych metodach klasy. Dodatkowo, zdefiniowaliśmy nową metodę is_alive
, która zwraca True
, jeśli postać ma więcej niż 0 punktów życia albo False
w przeciwnym przypadku.
Utworzenie obiektu klasy odbywa się poprzez wywołanie nazwy klasy jak funkcji. W przypadku klasy Character
wygląda to tak:
= Character("John the Brave", 100, ["sword", "shield"]) character1
Możliwe jest również modyfikowanie atrybutów obiektu:
= Character("John the Brave", 100, ["sword", "shield"])
character1 print(character1.name) # John the Brave
= "John the Wise"
character1.name print(character1.name) # John the Wise
💥 Zadanie 1 💥
Zmodyfikuj metodę introduce
tak, aby wypisywała imię i przedmioty postaci, np.:
Hello! My name is John the Brave and I have:
- sword
- shield
Wywołaj metodę introduce
na obiekcie character1
, następnie zmień imię postaci na “John the Wise” i wywołaj metodę introduce
ponownie.
Podpowiedź
Metoda introduce
powinna iterować po liście przedmiotów i wypisywać je na ekran:
for item in self.items:
Wyświetlanie zmiennych korzystając z f-stringów:
print(f"Hello! My name is {self.name} and I have:")
💥 Zadanie 2 💥
Dodaj do klasy Character
metodę take_damage
, która przyjmuje jako argument ilość punktów obrażeń. Metoda powinna odjąć tę wartość od punktów życia postaci. Jeśli po odjęciu obrażeń postać ma mniej niż 0 punktów życia, to metoda powinna ustawić punkty życia na 0.
Operatory
Python pozwala na przeciążanie operatorów. Oznacza to, że można zdefiniować własne zachowanie operatorów dla obiektów danej klasy. Przykładowo, jeśli chcemy dodać dwa obiekty klasy ComplexNumber
, to możemy zdefiniować zachowanie operatora +
, poprzez zdefiniowanie metody __add__
(dwa znaki _
przed i po add
, takie metody nazywa się magicznymi, ang. magic methods). :
class ComplexNumber:
def __init__(self, real, imag):
self.real = real
self.imaginary = imag
# Zdefiniowanie metody __add__ pozwala na dodawanie dwóch obiektów klasy ComplexNumber korzystając z operatora +
def __add__(self, other):
# Zwracamy nowy obiekt klasy ComplexNumber, z częścią rzeczywistą równą sumie części rzeczywistych obu obiektów i częścią urojoną równą sumie części urojonych obu obiektów
return ComplexNumber(self.real + other.real, self.imaginary + other.imaginary)
Metody magiczne
Operator | Metoda | Znaczenie |
---|---|---|
+ |
__add__ |
Dodawanie |
- |
__sub__ |
Odejmowanie |
* |
__mul__ |
Mnożenie |
/ |
__truediv__ |
Dzielenie |
// |
__floordiv__ |
Dzielenie całkowite |
% |
__mod__ |
Reszta z dzielenia |
** |
__pow__ |
Potęgowanie |
== |
__eq__ |
Równość |
!= |
__ne__ |
Nierówność |
Pozostałe metody magiczne, wraz z przykładami użycia, można znaleźć np. tutaj.
💥 Zadanie 3 💥
Zdefiniuj klasę Vector
, która będzie przechowywała dwie współrzędne: x
i y
. Zdefiniuj metody pozwalające na dodawanie, odejmowanie i mnożenie wektorów. Metody te powinny zwracać nowy obiekt klasy Vector
z odpowiednio zmodyfikowanymi współrzędnymi.
Podpowiedź
Zdefiniuj metody __add__
, __sub__
i __mul__
w klasie Vector
.
💥 Zadanie 4 💥
Zdefiniuj klasę Matrix
, która będzie przechowywała dwuwymiarową macierz. Zdefiniuj metody pozwalające na dodawanie, odejmowanie i mnożenie macierzy. Metody te powinny zwracać nowy obiekt klasy Matrix
z odpowiednio zmodyfikowanymi wartościami macierzy.
Podpowiedź
Macierz można reprezentować jako listę list. Przykładowa macierz 2x3:
[[1, 2, 3],
[4, 5, 6]]
Zdefiniuj metody __add__
, __sub__
i __mul__
w klasie Matrix
.
Dziedziczenie
Dziedziczenie to mechanizm, który pozwala na tworzenie nowych klas na podstawie już istniejących. Klasa dziedzicząca nazywana jest klasą pochodną, a klasa, z której dziedziczy, nazywana jest klasą bazową. Klasa pochodna dziedziczy atrybuty i metody klasy bazowej. Dzięki dziedziczeniu można unikać powtarzania kodu, a także tworzyć hierarchię klas.
W ramach przykładu wróćmy do klasy Character
. Załóżmy, że chcemy stworzyć nową klasę Wizard
, która będzie dziedziczyła po klasie Character
. Klasa Wizard
będzie miała dodatkowy atrybut mana
oraz metodę cast_spell
. Dzięki dziedziczeniu nie musimy ponownie definiować atrybutów i metod klasy Character
.
class Character:
def __init__(self, name, health, items):
self.name = name
self.health = health
self.items = items
def introduce(self):
print("Hello!")
def is_alive(self):
return self.health > 0
class Wizard(Character):
def __init__(self, name, health, items, mana):
super().__init__(name, health, items)
self.mana = mana
def cast_spell(self):
if self.mana > 0:
print("Fireball!")
self.mana -= 1
else:
print("Not enough mana!")
Spróbuj utworzyć obiekt klasy Wizard
i wywołać metodę cast_spell
oraz introduce
.
💥 Zadanie 5 💥
Zdefiniuj klasę Warrior
, która dziedziczy po klasie Character
. Klasa Warrior
powinna mieć dodatkowy atrybut strength
oraz metodę attack
, która wypisuje “Attack!” używając funkcji print
oraz zwraca wartość siły (strength
). Utwórz obiekt klasy Warrior
i wywołaj metodę attack
.
Zadanie końcowe
Dana jest klasa bazowa Account
, która przechowuje informacje o numerze konta i saldzie.
class Account:
def __init__(self, account_number, balance):
self.account_number = account_number
self.balance = balance
def __str__(self): # Metoda __str__ pozwala na zdefiniowanie reprezentacji obiektu jako string
return f"Account number: {self.account_number}, balance: {self.balance}"
- Do klasy
Account
dodaj metodę__add__
i__sub__
, które pozwolą na dodawanie i odejmowanie środków z konta. - Zdefiniuj klasę
SavingsAccount
, która dziedziczy po klasieAccount
. KlasaSavingsAccount
powinna dodatkowo przechowywać informację o oprocentowaniu. - Do klasy
SavingsAccount
dodaj metodęadd_interest
, która zwiększa saldo o wartość oprocentowania. - Zdefiniuj klasę
CheckingAccount
, która dziedziczy po klasieAccount
. KlasaCheckingAccount
powinna dodatkowo przechowywać informację o liczbie darmowych przelewów oraz opłacie za przelew. - Do klasy
CheckingAccount
dodaj metodętransfer
, która przyjmuje jako argument drugie konto oraz kwotę przelewu. Metoda powinna sprawdzać, czy liczba darmowych przelewów nie została przekroczona i czy na koncie występują środki pozwalające na przelanie środków i poniesienie ewentualnych opłat. Jeśli tak, to powinna obciążyć konto opłatą za przelew. W przeciwnym przypadku powinna zrealizować przelew.
Przykładowe wykorzystanie:
= SavingsAccount("1234", 1000, 0.05) # numer konta, saldo, oprocentowanie
account1 = CheckingAccount("5678", 500, 3, 5) # numer konta, saldo, liczba darmowych przelewów, opłata za przelew
account2
= account1 + 50
account1 print(account1) # Account number: 1234, balance: 1050.0
= account1 - 50
account1 print(account1) # Account number: 1234, balance: 1000.0
account1.add_interest()print(account1) # Account number: 1234, balance: 1050.0
100)
account2.transfer(account1, print(account2) # Account number: 5678, balance: 400
print(account1) # Account number: 1234, balance: 1150.0
Zadanie dla chętnych
Rozbuduj przykład z klasą Character
o klasy Knight
, Archer
i Rogue
. Każda z tych klas powinna dziedziczyć po klasie Character
i posiadać dodatkowe atrybuty i metody.
- Dodaj metodę
fight
, która przyjmuje drugą postać i zwraca wynik walki. Wynik walki powinien być losowy, ale zależny od rozmiaru listy przedmiotów postaci i jej punktów życia (losowość w Python) - Dodaj metodę
heal
, która przywraca postaci punkty życia. Metoda powinna przyjmować jako argument ilość punktów życia do przywrócenia. - Dodaj metodę
equip
, która pozwala na dodanie przedmiotu do listy przedmiotów postaci. - Dodaj metodę
unequip
, która pozwala na usunięcie przedmiotu z listy przedmiotów postaci. Jeśli przedmiotu nie ma w liście, to metoda powinna zwrócićFalse
, w przeciwnym przypadkuTrue
. - Dodaj metodę
steal
, która pozwala na kradzież losowego przedmiotu z listy przedmiotów drugiej postaci. Metoda powinna przyjmować drugą postać jako argument. - Dodaj klasę
Game
, która będzie przechowywała listę postaci. KlasaGame
powinna posiadać metodęfight
, która wywołuje metodęfight
dla dwóch losowo wybranych postaci z listy. Metoda powinna zwracać zwycięzcę walki. - Dodaj metodę
add_character
, która pozwala na dodanie postaci do listy. - Dodaj metodę
remove_character
, która pozwala na usunięcie postaci z listy. - Dodaj metodę
add_random_character
, która dodaje do listy postać losowego typu (Knight, Archer, Rogue, Wizard) z losowymi atrybutami.