U ovom tekstu ćete kreirati aplikaciju za tablicu množenja, koristeći principe objektno orijentisanog programiranja (OOP) u programskom jeziku Python.
Ovaj projekat će vam omogućiti da praktikujete ključne koncepte OOP-a i demonstrira kako se oni mogu primeniti u potpuno funkcionalnoj aplikaciji.
Python je višenamenski programski jezik, što znači da programeri mogu odabrati najprikladniji pristup za svaku konkretnu situaciju i izazov. Objektno orijentisano programiranje se ističe kao jedan od najčešće korišćenih modela za razvoj skalabilnih aplikacija u poslednjih nekoliko decenija.
Osnove OOP-a
Razmotrićemo ukratko najvažniji koncept OOP-a u Pythonu: klase.
Klasa predstavlja šablon unutar kojeg definišemo strukturu i ponašanje objekata. Ovaj šablon nam omogućava da formiramo instance, koje su zapravo pojedinačni objekti kreirani prema specifikacijama klase.
Jednostavna klasa koja predstavlja knjigu, sa atributima naslova i boje, mogla bi se definisati na sledeći način:
class Book: def __init__(self, title, color): self.title = title self.color = color
Za kreiranje instanci klase `Book`, potrebno je pozvati klasu i proslediti joj argumente:
# Instance objects of Book class blue_book = Book("The blue kid", "Blue") green_book = Book("The frog story", "Green")
Vizuelni prikaz trenutne strukture našeg programa bio bi sledeći:
Zanimljivo je da kada proverimo tip instanci `blue_book` i `green_book`, dobijamo rezultat „Book“, što ukazuje da su oba objekta instanca klase `Book`.
# Printing the type of the books print(type(blue_book)) # <class '__main__.Book'> print(type(green_book)) # <class '__main__.Book'>
Nakon što su ovi koncepti razjašnjeni, možemo preći na razvoj našeg projekta 😃.
Opis Projekta
U praksi, kao programeri, većinu vremena ne provodimo u pisanju koda. Prema izvoru sa thenewstack, samo trećinu našeg vremena posvećujemo pisanju ili refaktorisanju koda.
Preostale dve trećine vremena utrošimo na razumevanje tuđeg koda i analizu problema na kojem radimo.
Stoga ću za ovaj projekat definisati problem i analizirati pristup za razvoj naše aplikacije. Na taj način, prolazimo kroz celokupan proces, od konceptualizacije rešenja do implementacije koda.
Učitelj želi da kreira igru koja će testirati veštine množenja učenika uzrasta od 8 do 10 godina.
Igra treba da ima sistem života i bodovanja, gde učenik počinje sa 3 života i mora da dostigne određen broj poena za pobedu. Ukoliko učenik izgubi sve živote, program treba da prikaže poruku „izgubio si“.
Igra treba da ima dva načina rada: slučajno množenje i tablicu množenja.
U prvom načinu, učeniku se prikazuje nasumičan zadatak množenja brojeva od 1 do 10, i mora tačno da odgovori kako bi osvojio poen. Ako ne odgovori tačno, učenik gubi život i igra se nastavlja. Učenik pobeđuje tek kada dostigne 5 poena.
U drugom načinu, prikazuje se tablica množenja brojeva od 1 do 10, gde učenik mora da unese rezultat odgovarajućeg množenja. Ako učenik tri puta pogreši, gubi, ali ako popuni dve tablice, igra se završava.
Svestan sam da su zahtevi možda malo opširni, ali obećavam da ćemo ih rešiti u ovom tekstu 😁.
Podeli pa vladaj
Ključna veština u programiranju je sposobnost rešavanja problema. To podrazumeva da morate imati plan pre nego što počnete da pišete kod.
Uvek preporučujem da razložite složene probleme na manje, lakše rešive delove.
Dakle, ako treba da razvijete igru, počnite tako što ćete je podeliti na ključne delove. Ove podprobleme će biti mnogo lakše rešiti.
Tek nakon toga, imaćete jasnu viziju kako implementirati i integrisati sve delove u kod.
Hajde da napravimo dijagram koji će ilustrovati strukturu igre.
Ovaj dijagram prikazuje odnose između objekata naše aplikacije. Glavna dva objekta su slučajno množenje i tablica množenja. Zajednički atributi su im poeni i životi.
Sada, sa svim ovim informacijama, prelazimo na kod.
Kreiranje bazne klase `Igra`
Kada radimo sa objektno orijentisanim programiranjem, težimo ka tome da izbegnemo ponavljanje koda. Ovo se često naziva DRY (ne ponavljaj se).
Napomena: Ovaj cilj se ne odnosi na pisanje što manje linija koda (kvalitet koda se ne meri samo tim aspektom), već na apstrakciju logike koja se često koristi.
S obzirom na prethodno, bazna klasa naše aplikacije treba da postavi osnovnu strukturu i ponašanje za druge dve klase.
Da vidimo kako se to postiže:
class BaseGame: # Dužina poruke koja je centrirana message_lenght = 60 description = "" def __init__(self, points_to_win, n_lives=3): """Bazna klasa igre Args: points_to_win (int): Broj poena potreban za pobedu n_lives (int): Broj života igrača. Podrazumevano 3. """ self.points_to_win = points_to_win self.points = 0 self.lives = n_lives def get_numeric_input(self, message=""): while True: # Uzima unos od korisnika user_input = input(message) # Ako je unos numerički, vraća ga # Ako nije, ispisuje poruku i ponavlja if user_input.isnumeric(): return int(user_input) else: print("Unos mora biti broj") continue def print_welcome_message(self): print("PYTHON IGRA MNOŽENJA".center(self.message_lenght)) def print_lose_message(self): print("ŽAO MI JE, IZGUBILI STE SVE ŽIVOTE".center(self.message_lenght)) def print_win_message(self): print(f"ČESTITAMO, DOSTIGLI STE {self.points} POENA".center(self.message_lenght)) def print_current_lives(self): print(f"Trenutno imate {self.lives} života") def print_current_score(self): print(f"Vaš rezultat je {self.points}") def print_description(self): print("nn" + self.description.center(self.message_lenght) + "n") # Osnovni metod za pokretanje def run(self): self.print_welcome_message() self.print_description()
Ova klasa izgleda prilično opširno. Dozvolite mi da je detaljno objasnim.
Pre svega, razjasnićemo atribute klase i konstruktor.
Atributi klase su promenljive definisane unutar klase, ali izvan konstruktora ili bilo kog metoda.
Atributi instance su promenljive koje se kreiraju isključivo unutar konstruktora.
Glavna razlika između ova dva tipa atributa je opseg važenja. Atributi klase su dostupni i iz objekta instance i iz same klase. Atributi instance su dostupni samo iz objekta instance.
game = BaseGame(5) # Pristupanje atributu klase message_lenght iz klase print(game.message_lenght) # 60 # Pristupanje atributu klase message_lenght iz klase print(BaseGame.message_lenght) # 60 # Pristupanje atributu instance points iz instance print(game.points) # 0 # Pristupanje atributu instance points iz klase print(BaseGame.points) # Greška atributa
Ova tema bi mogla biti detaljnije razmatrana u zasebnom članku. Pratite nas za više informacija.
Funkcija `get_numeric_input` se koristi kako bi se osiguralo da korisnik unese samo numeričke vrednosti. Ovaj metod kontinuirano traži unos od korisnika dok ne primi numerički unos. Koristićemo ga kasnije u podklasama.
Metode za ispis poruka omogućavaju izbegavanje ponavljanja istog ispisa svaki put kada se dogodi određeni događaj u igri.
Konačno, metod `run` je osnovni metod koji će klase `RandomMultiplication` i `TableMultiplication` koristiti za interakciju sa korisnikom i izvršavanje glavnih funkcionalnosti.
Kreiranje podklasa
Nakon što smo kreirali baznu klasu koja postavlja osnovnu strukturu i neke od funkcionalnosti naše aplikacije, vreme je da kreiramo klase koje implementiraju specifične režime igre, koristeći moć nasleđivanja.
Klasa `SlučajnoMnoženje`
Ova klasa pokreće prvi režim naše igre. Ona koristi modul `random` kako bi generisala slučajne operacije množenja brojeva od 1 do 10. Evo odličnog članka o modulu random (i drugim važnim modulima) 😉.
import random # Modul za slučajne operacije
class RandomMultiplication(BaseGame): description = "U ovoj igri morate tačno odgovoriti na slučajne operacije množenja.nPobeđujete ako sakupite 5 poena ili gubite ako izgubite sve živote." def __init__(self): # Broj poena potreban za pobedu je 5 # Prosleđuje se argument 5 "points_to_win" super().__init__(5) def get_random_numbers(self): first_number = random.randint(1, 10) second_number = random.randint(1, 10) return first_number, second_number def run(self): # Poziva metodu iz bazne klase da ispiše uvodne poruke super().run() while self.lives > 0 and self.points_to_win > self.points: # Uzima dva slučajna broja number1, number2 = self.get_random_numbers() operation = f"{number1} x {number2}: " # Zahteva od korisnika da unese rezultat operacije # Sprečava greške sa vrednostima user_answer = self.get_numeric_input(message=operation) if user_answer == number1 * number2: print("nVaš odgovor je tačann") # Dodaje poen self.points += 1 else: print("nŽao mi je, vaš odgovor nije tačann") # Oduzima život self.lives -= 1 self.print_current_score() self.print_current_lives() # Izvršava se samo kada se igra završi # I nijedan od uslova nije ispunjen else: # Ispisuje finalnu poruku if self.points >= self.points_to_win: self.print_win_message() else: self.print_lose_message()
Ovo je još jedna opširna klasa 😅. Ali kao što sam ranije napomenuo, ne radi se o broju linija, već o čitljivosti i efikasnosti koda. Jedna od najboljih karakteristika Pythona je što omogućava programerima da pišu čist i razumljiv kod, kao da pišu običan engleski jezik.
U ovoj klasi postoji jedan detalj koji vas može zbuniti, ali ću ga objasniti što je jednostavnije moguće.
# Bazna klasa def __init__(self, points_to_win, n_lives=3): "... # Podklasa def __init__(self): # Broj poena potreban za pobedu je 5 # Prosleđuje se argument 5 "points_to_win" super().__init__(5)
Konstruktor podklase poziva funkciju `super` koja se odnosi na baznu (BaseGame) klasu. U osnovi, ovo govori Pythonu:
Postavite atribut „points_to_win“ bazne klase na vrednost 5!
Nije potrebno navoditi `self` unutar `super().__init__()` zato što pozivamo super unutar konstruktora i to bi bilo suvišno.
Takođe koristimo funkciju super unutar metode `run`. Pogledajmo šta se dešava u tom delu koda.
# Osnovni metod za pokretanje # Metod bazne klase def run(self): self.print_welcome_message() self.print_description() def run(self): # Poziva metodu bazne klase da ispiše uvodne poruke super().run() .....
Kao što možete videti, metod `run` u baznoj klasi ispisuje poruku dobrodošlice i opis. Dobro je sačuvati tu funkcionalnost i dodati dodatne elemente u podklasama. Stoga, koristimo `super` da izvršimo sav kod baznog metoda pre nastavka izvršavanja narednog dela.
Drugi deo funkcije `run` je prilično jednostavan. Zahteva od korisnika unos broja kao odgovor na operaciju množenja. Zatim se rezultat poredi sa tačnim rezultatom množenja i ako su jednaki, dodaje se poen; ako nisu, oduzima se 1 život.
Vredi napomenuti da koristimo `while-else` petlje. Ovo je izvan opsega ovog članka, ali ću objaviti tekst o tome u narednim danima.
Konačno, `get_random_numbers` koristi funkciju `random.randint` koja vraća slučajni celi broj unutar specificiranog raspona. Zatim, vraća skup od dva slučajna cela broja.
Klasa `TablicaMnoženja`
Drugi režim igre prikazuje tablicu množenja i proverava da li je korisnik tačno rešio najmanje dve tablice.
Za ovo ćemo ponovo koristiti moć funkcije `super` i izmeniti atribut `point_to_win` roditeljske klase na 2.
class TableMultiplication(BaseGame): description = "U ovoj igri morate tačno rešiti celu tablicu množenja.nPobeđujete ako tačno rešite 2 tablice." def __init__(self): # Potrebno je tačno rešiti 2 tablice za pobedu super().__init__(2) def run(self): # Ispisuje uvodne poruke super().run() while self.lives > 0 and self.points_to_win > self.points: # Uzima slučajni broj number = random.randint(1, 10) for i in range(1, 11): if self.lives <= 0: # Osigurava da se igra ne nastavlja # ako korisnik izgubi sve živote self.points = 0 break operation = f"{number} x {i}: " user_answer = self.get_numeric_input(message=operation) if user_answer == number * i: print("Odlično! Vaš odgovor je tačan") else: print("Žao mi je, vaš odgovor nije tačan") self.lives -= 1 self.points += 1 # Izvršava se samo kada se igra završi # I nijedan od uslova nije ispunjen else: # Ispisuje finalnu poruku if self.points >= self.points_to_win: self.print_win_message() else: self.print_lose_message()
Kao što možete primetiti, izmenili smo samo metod `run` ove klase. To je moć nasleđivanja: pišemo logiku jednom i koristimo je na više mesta 😅.
U metodu `run` koristimo `for` petlju da dobijemo brojeve od 1 do 10 i generišemo operaciju koja se prikazuje korisniku.
Ako se životi iscrpe ili se dostigne broj poena potreban za pobedu, `while` petlja se prekida i prikazuje se poruka o pobedi ili porazu.
DA, kreirali smo dva režima igre, ali za sada, ako pokrenemo program, ništa se neće dogoditi.
Dakle, finalizirajmo program implementacijom izbora režima i instanciranjem klasa u zavisnosti od tog izbora.
Implementacija Izbora Režima
Korisnik treba da može da odabere režim igre koji želi da igra. Da vidimo kako to implementirati.
if __name__ == "__main__": print("Odaberite režim igre") choice = input("[1],[2]: ") if choice == "1": game = RandomMultiplication() elif choice == "2": game = TableMultiplication() else: print("Molim vas, odaberite validan režim igre") exit() game.run()
Prvo, tražimo od korisnika da odabere između režima 1 ili 2. Ako unos nije validan, skripta se zaustavlja. Ako korisnik odabere prvi režim, program pokreće režim nasumičnog množenja. Ako odabere drugi režim, pokreće se režim tablice množenja.
Ovako bi to izgledalo:
Zaključak
Čestitamo, upravo ste kreirali Python aplikaciju koristeći objektno orijentisano programiranje.
Kompletan kod je dostupan na GitHub repozitorijumu.
U ovom tekstu ste naučili kako da:
- Koristite konstruktore u Python klasama
- Napravite funkcionalnu aplikaciju koristeći OOP
- Koristite funkciju super u Python klasama
- Primienite osnovne koncepte nasleđivanja
- Implementirate atribute klasa i instance
Srećno kodiranje 👨💻
Sledeće, istražite neke od najboljih Python IDE-ova za bolju produktivnost.