Lekcja 9- pliki cd., funkcje, klasy, programowanie obiektowe
-PROGRAMOWANIE OBIEKTOWE: (ang. object-oriented programming, OOP) to sposób tworzenia oprogramowania, w którym obiekty ze świata rzeczywistego przedstawiamy jako obiekty programowe, gdzie każdy z nich posiada swoje cechy (zwane atrybutami) oraz może wykonywać jakieś czynności (nazywane metodami). Innymi słowy – łączymy dane z operacjami, które mogą być na tych danych przeprowadzane.
Wyobraźmy sobie, że mamy zaprogramować prostego robota imitującego zachowania psa. W takiej sytuacji możemy przedstawić go w formie obiektu programowego o nazwie Dog o następujących atrybutach:
- imię,
- wiek,
- waga,
- rasa,
- kolor umaszczenia.
Ponadto, jak w przypadku typowego psa, chcemy, aby nasz robot potrafił:
- szczekać,
- aportować,
- merdać ogonem,
co, stosując zasady programowania obiektowego, implementujemy w formie metod. [1]
Języki oparte na programowaniu obiektowym: Java, C#, C++, PHP, JavaScript czy nasz Python
-FUNKCJE:
def
return zwraca wartość, choć sama funkcja nie zawsze zwraca nam jakąś wartość
jeśli tworzymy własne funkcje, to podajemy jakiego parametru się spodziewamy: int, str, tuple itd. to zasady savoir-vivre Pythonowego
-KLASY:
Klasa to szablon, na podstawie którego tworzone są obiekty.
nowe klasy tworzymy stylem PascalCase, nie snake_case jak resztę- (więcej w PEP8)
init to specjalna funkcja, która uruchamia się przy tworzeniu obiektu; init to nie jest konstruktor, jest wywoływany po stworzeniu obiektu
Do czego można zastosować klasę w programie z książką telefoniczną: klasa jako wpisy w książce, ale też może być osobna klasa dla książki.
Klasę można dziedziczyć. (Klasa- rodzic, podklasa- dziecko). Programując raczej rzadko wykorzystujemy dziedziczenie klas, bo może nam się przez to wysypać później program, ale samo dziedziczenie generalnie nie jest złe.
Problemy dziedziczenia klas:
- Ścisłe sprzężenie : Dziedziczenie tworzy silne sprzężenie między klasami rodzica i dzieci. Zmiany w klasie mogą nieumyślnie wpłynąć na wszystkie podklasy, a to może prowadzić do problemów i błędów w programie.
- Kompozycja ponad dziedziczeniem : Wielu praktyków OOP opowiada się za stosowaniem kompozycji (gdzie klasy są konstruowane przy użyciu instancji innych klas) zamiast dziedziczenia. Kompozycja ma tendencję do oferowania większej elastyczności i zmniejsza ryzyko związane ze ścisłym sprzężeniem.
- Trudności w zrozumieniu : Głębokie hierarchie dziedziczenia mogą utrudniać zrozumienie bazy kodu. Deweloperzy mogą mieć trudności ze znalezieniem źródła metod lub właściwości, zwłaszcza jeśli w grę wchodzą różne poziomy dziedziczenia.
- Rozrost kodu : dziedziczenie może prowadzić do duplikacji kodu, gdy podklasy nadpisują metody bez dodawania znaczącej nowej funkcjonalności. W efekcie powstają większe, mniej wydajne bazy kodu.
- Naruszenie zasady podstawienia Liskova : W niektórych przypadkach klasy pochodne mogą zachowywać się inaczej niż się spodziewano, gdy są podstawiane do ich klasy bazowej. Może to prowadzić do problemów, które naruszają zasadę substytucyjności, czyniąc polimorfizm problematycznym.
Kompozycję stosuje się wtedy, gdy między klasami zachodzi relacja typu „całość ↔ część” tzn. nowa klasa zawiera w sobie istniejącą klasę.
Przykład z zajęć:
class Profesja: # opisujemy klasę
class PostacGracza:
def __init__ (self, imie: str, profesja: Profesja) #atrybut profesja jest klasą
self.profesja = Profesja
Dziedziczenie stosuje się wtedy, gdy między klasami zachodzi relacja „generalizacja ↔ specjalizacja” tzn. nowa klasa jest szczególnym rodzajem już istniejącej klasy.
Przykład z zajęć:
class Mag(PostacGracza):
def __init__(self, imie, profesja, hp = 100, mana = 200): # tu wpisujemy wszystkie odziedziczone atrybuty + nowy, wynikajacy z naszej klasy
super()__init__(imie, profesja, hp, mana) # tu wpisujemy w nawias wszystko co dziedziczymy po klasie PostacGracza (już bez self)
W Pythonie super()
jest to wbudowana funkcja używana do wywoływania metod zdefiniowanych w klasie nadrzędnej (PostacGracza).
Notatki z zajęć- gra RPG:
class PostacGracza:
def __init__(self, imie: str, rasa: str, profesja: str) -> None: #, hp: int, mana: int
self.imie = imie
self.rasa = rasa
self.profesja = profesja
self.hp = 100
self.mana = 100
def otrzymaj_obrazenia(self, ilosc_obrazen: int):
self.hp -= ilosc_obrazen
def powitanie(self):
print(f"Witaj {self.imie}.")
gracz2 = PostacGracza("Taz", "kender", "łotrzyk")
print(f"{gracz2.imie} ma na poczatku {gracz2.hp}")
gracz2.otrzymaj_obrazenia(20)
print(f"{gracz2.imie} ma po otrzymaniu obrażeń {gracz2.hp}")
def stworz_gracza():
imie = input("Podaj imie: ")
rasa = input("Podaj rase: ")
profesja = input("Podaj profesje: ")
return PostacGracza(imie, rasa, profesja)
gracz3 = stworz_gracza()
print(f"{gracz3.imie} ma na początku {gracz3.hp} hp")
class Mag(PostacGracza): #Klasa PostacGracza to klasa rodzica, klasa Mag to dzieci (potomkowie)
def __init__(self, imie, hp=100, mana=200):
super().__init__(imie, hp, mana)
def kula_ognia(self):
self.mana -= 10
gracz1 = Mag("Fizban")
gracz1.powitanie()
# gracz1 = PostacGracza("Fizban", "czlowiek", "mag", 100, 100)
# gracz2 = PostacGracza("Taz", "kender", "łotrzyk", 100, 100)
# print(f"{gracz1.imie} jest typu {type(gracz1)}") #skonstruowalismy wlasny typ obiektu PostacGracza
# print(f"{gracz2.imie} jest typu {type(gracz2)}")
#Duzo wpisywania, sensownie by bylo miec jakies wartosci domyslne
#Jak podamy w zlej kolejnosci to sie pomiesza
#W przypadku bardziej skomplikowanych klas robi się tak:
# gracz1 = PostacGracza(imie="Fizban", rasa="czlowiek", profesja="mag", hp=100, mana=100)
# gracz2 = PostacGracza(imie="Taz", rasa="kender", profesja="łotrzyk", hp=100, mana=100)