Dio 1 – Objekat i klasa
Dio 2 – Veze i višestrukost
Dio 3 – Domenski model DeliveryApp
Praksa – classDiagram; kostur klasa u Pythonu
Materijale priredio Erkan Islamović
erkan.islamovic@eubd.edu.ba · erkan.islamovic@gmail.com
Petnaest sedmica, svaka s tri časa teorije i tri časa prakse. Danas: poglavlje 6 (Sedmica 6, označeno). Isti primjer kroz knjigu: DeliveryApp, narudžba N-1001 (24,50 €).
| Sed. | Poglavlje | Sed. | Poglavlje |
|---|---|---|---|
| 1 | P1 Zašto projektujemo | 9 | P9 Arhitektura |
| 2 | P2 Životni ciklus | 10 | P10 Integracija i API |
| 3 | P3 Zahtjevi | 11 | P11 Korisnički interfejs |
| 4 | P4 Poslovni procesi | 12 | P12 Sigurnost i privatnost |
| 5 | P5 UML i slučajevi korištenja | 13 | P13 Realno vrijeme i IoT |
| 6 | P6 Klase i objekti | 14 | P14 Kvalitet i isporuka |
| 7 | P7 Sekvence i stanja | 15 | P15 AI i završni projekat |
| 8 | P8 Baza podataka |
Početkom šezdesetih godina dvojica norveških naučnika, Ole-Johan Dahl i Kristen Nygaard, radili su u Norveškom računskom centru u Oslu na simulacijama: kako se ponaša red brodova u luci, kako teče posao u fabrici, gdje se stvaraju gužve. Programi tog vremena bili su dugi spiskovi naredbi, i u njima se teško vidjelo šta je brod, a šta dizalica. Dahl i Nygaard su smislili drugačiji način: program se sastoji od objekata koji liče na stvari iz stvarnog svijeta, a svaki objekat ima svoje podatke i svoje ponašanje. Opis po kojem se prave svi brodovi iste vrste nazvali su klasa. Jezik koji su napravili zvao se Simula, a njegova verzija iz 1967. godine prvi je jezik s klasama, objektima i nasljeđivanjem. Kasnije su iz te ideje nastali Smalltalk, C++, Java, Python i C#, pa danas skoro svaki programer misli u objektima. Dahl i Nygaard su 2001. godine dobili Turingovu nagradu, koja se često naziva „Nobelovom nagradom za računarstvo”, upravo za objektno orijentisano programiranje.
U poglavlju 5 opisali smo šta DeliveryApp radi za svakog aktera. Sada pitamo: šta DeliveryApp zna? Koje „stvari” postoje u sistemu, šta o svakoj pamti i kako su povezane? Odgovor daje strukturni model, a u UML-u ga crtamo dijagramom klasa. Prvo objašnjavamo objekat i klasu, uz Python kod. Zatim učimo veze među klasama: asocijaciju, višestrukost, kompoziciju i nasljeđivanje. Na kraju iz teksta vizije pronalazimo klase i crtamo domenski model DeliveryApp. Na praktičnoj nastavi isti model crtamo u Mermaidu i pišemo ga kao Python klase koje računaju 24,50 €.
Nakon poglavlja možete:
objasniti razliku između objekta i klase na primjeru narudžbe; za objekat navesti stanje, ponašanje i identitet; nacrtati klasu u UML-u s atributima, tipovima, operacijama i vidljivošću; objasniti enkapsulaciju i zašto stanje narudžbe ne mijenjamo direktno; nacrtati asocijaciju s višestrukošću i pročitati je kao rečenicu; razlikovati kompoziciju, agregaciju i običnu asocijaciju; prepoznati kada je nasljeđivanje dobar izbor, a kada nije; iz teksta vizije izdvojiti kandidate za klase, atribute i operacije; nacrtati domenski model DeliveryApp u Mermaidu; napisati kostur klasa u Pythonu i provjeriti da daje 24,50 € za N-1001; nacrtati dijagram objekata za N-1001.
| Čas | Sadržaj | Na času |
|---|---|---|
| Teorija 1 | 6.1–6.3 Objekat, klasa, enkapsulacija | Kalup i kolači; klasa Narudžba u UML-u i Pythonu |
| Teorija 2 | 6.4–6.6 Asocijacija, višestrukost, kompozicija, nasljeđivanje | „Jedan kupac pravi nula ili više narudžbi” |
| Teorija 3 | 6.7–6.9 Od imenica do klasa; domenski model; objekti | Domenski model DeliveryApp; N-1001 kao objekti |
| Praksa 1–3 | Mermaid classDiagram; Python klase | model.py računa 24,50 € |
Objekat je jedna konkretna stvar u sistemu. Narudžba N-1001 je objekat. Picerija R-1 je objekat. Pizza Margarita na jelovniku R-1 je objekat. Svaki objekat ima tri osobine:
Objekat može predstavljati i nešto što se ne može dodirnuti: narudžbu, plaćanje, kod popusta. Pravilo je jednostavno: ako sistem o nečemu mora pamtiti podatke i to nešto treba razlikovati od drugih iste vrste, to je kandidat za objekat.
Klasa je opis po kojem se prave svi objekti iste vrste. Klasa Narudžba kaže: svaka narudžba ima broj, kupca, restoran, udaljenost, stavke i stanje, i svaka zna izračunati ukupnu cijenu. Svaki pojedinačni objekat (N-1001, N-1002…) je instanca te klase: ima istu građu, ali svoje vrijednosti.
Najbolja slika iz života je kalup za kolače. Kalup je klasa: određuje oblik. Kolači su objekti: svaki ima isti oblik, ali jedan je s čokoladom, drugi s vanilom, a treći je pojeden. Kalup se ne jede; kolač se jede. Isto tako, klasa Narudžba ne košta ništa; narudžba N-1001 košta 24,50 €.
U dijagramu klasa klasa se crta kao pravougaonik s tri dijela:
ime: tip;ime(parametri): tip rezultata.Ispred atributa i operacija stoji znak vidljivosti: + znači javno (svi smiju koristiti), - znači privatno (samo sam objekat), # znači zaštićeno (objekat i njegove podklase). Evo klase Narudzba kao Mermaid teksta:
classDiagram
class Narudzba {
+broj: str
+udaljenost_km: float
-stanje: str
+dodaj(jelo: Jelo, kolicina: int)
+artikli() int
+dostava() int
+ukupno() int
+promijeni(novo: str)
}Primijetite da u imenima pišemo „Narudzba” bez slova ž. Mermaid i većina programskih jezika dozvoljavaju i naša slova, ali mnogi alati i baze s njima imaju problema, pa je dobra navika da se u imenima u kodu koriste samo engleska slova, a u tekstu za ljude puni dijakritici.
Dijagram i kod opisuju isto. Ovako izgleda dio klase u Pythonu (cijeli fajl je primjeri/deliveryapp/model.py):
@dataclass
class Narudzba:
broj: str
kupac: Kupac
restoran: Restoran
udaljenost_km: float
kod: str | None = None
stavke: list = field(default_factory=list)
stanje: str = "NOVA"
def dodaj(self, jelo, kolicina=1):
self.stavke.append(Stavka(jelo, kolicina))
def artikli(self):
return sum(s.iznos() for s in self.stavke)
def ukupno(self):
a = self.artikli()
return a - popust(a, self.kod) + self.dostava()Oznaka @dataclass kaže Pythonu da sam napravi dio koda koji puni atribute; mi pišemo samo atribute i operacije. Iznosi su u centima (950 znači 9,50 €), da bi se izbjegle greške zaokruživanja decimalnih brojeva u računaru; na ekranu ih pretvaramo nazad u eure.

Zamislite da svako ko ima pristup narudžbi može direktno napisati n.stanje = "OTKAZANA", u bilo kom trenutku. Šta sprečava da neko otkaže narudžbu koja je već na putu, suprotno pravilu PP-6? Ništa.
Enkapsulacija (od latinskog „u kapsulu”) znači da objekat skriva svoje unutrašnje podatke, a mijenja ih samo kroz svoje operacije, koje provjeravaju pravila. Narudžba ne dozvoljava da joj neko spolja postavi stanje; dozvoljava samo poziv promijeni("OTKAZANA"), a ta operacija prvo provjeri je li prelaz dozvoljen:
def promijeni(self, novo):
if novo not in PRELAZI[self.stanje]:
raise ValueError(f"nedozvoljen prelaz {self.stanje} -> {novo}")
self.stanje = novoZato je u UML-u atribut stanje označen s - (privatno), a operacija promijeni s + (javno).
Slika iz života: bankomat. Novac je u kapsuli; ne možete otvoriti bankomat i uzeti koliko hoćete. Možete samo pozvati operaciju „podigni 50 €”, a bankomat provjeri PIN i stanje računa prije nego što isplati. Pravila su na jednom mjestu, unutar objekta, i niko ih ne može zaobići.
„Enkapsulacija je skrivanje od drugih programera, iz nepovjerenja.” Nije. Enkapsulacija čuva pravila na jednom mjestu. Kad se pravilo PP-6 promijeni, mijenja se samo operacija promijeni, a ne stotinu mjesta u kodu koja su direktno pisala stanje.
Odgovori: tipka R ili klik na zamućeno polje.
Klase same nisu mnogo korisne. Narudžba mora znati čija je i iz kojeg restorana. Veza koja kaže da se objekti dvije klase „poznaju” zove se asocijacija i crta se linijom između klasa. Na krajevima linije piše višestrukost: koliko objekata s te strane može biti povezano s jednim objektom s druge strane.
| Oznaka | Značenje | Primjer |
|---|---|---|
1 | tačno jedan | narudžba ima tačno jednog kupca |
0..1 | nijedan ili jedan | narudžba ima nijednog ili jednog dostavljača (dok ga ne dobije) |
0.. ili | nijedan ili više | kupac može imati nula ili više narudžbi |
1..* | jedan ili više | narudžba ima bar jednu stavku |
Asocijacija se čita kao rečenica, u oba smjera. Za vezu Kupac "1" --> "0..*" Narudzba : pravi:
Ako se rečenica ne slaže sa stvarnošću, dijagram je pogrešan. Može li narudžba imati dva kupca? U DeliveryApp ne. Može li kupac postojati bez ijedne narudžbe? Da, tek se registrovao. Zato je višestrukost na strani narudžbe 0.., a ne 1...
Višestrukost nije sitnica. Ona direktno postaje pravilo u bazi (poglavlje 8) i provjera u kodu. Ako je na strani stavki 1..*, sistem ne smije sačuvati narudžbu bez ijedne stavke.
Pročitajte naglas, u oba smjera: Narudzba "0..*" --> "0..1" Dostavljac : nosi. Zašto je na strani dostavljača 0..1, a ne 1? (Jer narudžba u stanju NOVA još nema dostavljača.)
Neke asocijacije su posebne: jedna strana je cjelina, a druga njen dio. UML razlikuje dvije jačine te veze.
Kompozicija je jaka veza: dio ne postoji bez cjeline i nestaje zajedno s njom. Crta se punim rombom (◆) na strani cjeline. Stavka „2 × Pizza Margarita” nema smisla bez narudžbe N-1001: ako se narudžba obriše, briše se i stavka. Isto važi za jelo na jelovniku restorana u DeliveryApp: kad restoran napusti DeliveryApp, njegov jelovnik nestaje s njim.
Agregacija je slaba veza: dio postoji i sam, a cjelina ga samo okuplja. Crta se praznim rombom (◇). Dostavljač D-2 može biti u timu „Jutarnja smjena”, ali ako se tim raspusti, D-2 i dalje postoji i vozi.
Slika iz života: kuća i sobe su kompozicija; kad se kuća sruši, sobe nestaju s njom. Fudbalski tim i igrači su agregacija; kad se tim raspusti, igrači prelaze u druge timove.
U praksi se agregacija rijetko crta, jer se teško razlikuje od obične asocijacije. Kompozicija je korisnija, jer kaže nešto važno za bazu i kod: brisanje cjeline briše i dijelove.
Kupac, dostavljač i osoblje restorana imaju mnogo zajedničkog: svi se prijavljuju, svi imaju ime, e-mail i lozinku. Umjesto da ista tri atributa pišemo tri puta, napravimo opštu klasu Korisnik, a posebne klase je nasljeđuju: preuzimaju sve od nje i dodaju svoje. Kupac dodaje adrese, Dostavljac dodaje vrstu vozila.
U UML-u se nasljeđivanje (generalizacija) crta punom linijom sa šupljim trouglom na strani opšte klase. Čita se „je vrsta”: kupac je vrsta korisnika.
classDiagram
Korisnik <|-- Kupac
Korisnik <|-- Dostavljac
Korisnik <|-- OsobljeRestorana
class Korisnik {
+email: str
-lozinka_hes: str
+prijavi(email, lozinka) bool
}
class Kupac {
+adrese: list
}
class Dostavljac {
+vozilo: str
}Nasljeđivanje je moćno, ali se lako zloupotrebi. Dva testa:
U savremenom projektovanju često se kaže: radije sastavljaj nego nasljeđuj (composition over inheritance). Ako kupac i dostavljač samo dijele adresu, bolje je da oba imaju objekat Adresa nego da nasljeđuju klasu koja „ima adresu”.

Narudzba "1" -- "1.." Stavka. Jedna narudžba se sastoji od jedne ili više stavki, a stavka ne postoji bez svoje narudžbe.Odgovori: tipka R ili klik na zamućeno polje.
Odakle znamo koje klase DeliveryApp treba? Najjednostavnija tehnika je analiza teksta: uzmemo viziju, korisničke priče i specifikacije slučajeva korištenja i podvučemo imenice i glagole.
Iz specifikacije UC-01 (poglavlje 5): „Kupac bira jela i količine; sistem pokazuje zbir artikala. Kupac unosi adresu dostave. Sistem traži udaljenost od servisa mapa… Sistem pravi narudžbu u stanju NOVA i prikazuje je restoranu.”
Kandidate zatim sortiramo:
| Imenica | Odluka | Zašto |
|---|---|---|
| Kupac | klasa | sistem pamti njegove podatke i razlikuje kupce |
| Jelo | klasa | ima naziv i cijenu; restoran ih ima više |
| Količina | atribut (stavke) | broj, nema svoje ponašanje |
| Zbir artikala | operacija | računa se iz stavki, ne pamti se |
| Adresa | klasa ili atribut | klasa, ako kupac ima više adresa; inače atribut |
| Udaljenost | atribut (narudžbe) | broj u km |
| Servis mapa | nije klasa domena | vanjski sistem (akter), ne pojam posla |
| Sistem | odbaciti | to je DeliveryApp sam |
| Narudžba | klasa | srce sistema |
| Stanje | atribut | jedna od sedam vrijednosti |
| Restoran | klasa | ima jelovnik i narudžbe |
Primijetite tri pravila: ono što se računa nije atribut (zbir artikala računamo, ne pamtimo, jer bi inače mogao biti netačan); vanjski sistemi nisu klase domena; i „sistem” se odbacuje.
Domenski model je dijagram klasa koji opisuje pojmove posla i njihove veze, bez tehničkih detalja (nema baze, ekrana, servera). Razumije ga i vlasnik restorana: on će reći „narudžba uvijek ima bar jedno jelo”, a mi ćemo to pročitati kao višestrukost 1..*.
Domenski model DeliveryApp:
classDiagram
class Kupac {
+oznaka: str
+telefon: str
}
class Adresa {
+ulica: str
+broj: str
}
class Restoran {
+oznaka: str
+naziv: str
+otvoren: bool
}
class Jelo {
+naziv: str
+cijena: int
+dostupno: bool
}
class Narudzba {
+broj: str
+udaljenost_km: float
-stanje: str
+artikli() int
+dostava() int
+ukupno() int
+promijeni(novo)
}
class Stavka {
+kolicina: int
+iznos() int
}
class Dostavljac {
+oznaka: str
+vozilo: str
}
class Placanje {
+iznos: int
+odobreno: bool
}
class KodPopusta {
+kod: str
+procenat: int
+max_iznos: int
}
Kupac "1" --> "1..*" Adresa : ima
Kupac "1" --> "0..*" Narudzba : pravi
Restoran "1" *-- "1..*" Jelo : nudi
Narudzba "0..*" --> "1" Restoran : iz
Narudzba "1" *-- "1..*" Stavka : sadrži
Stavka "0..*" --> "1" Jelo : od
Narudzba "0..*" --> "0..1" Dostavljac : nosi
Narudzba "1" --> "0..1" Placanje : plaćena
Narudzba "0..*" --> "0..1" KodPopusta : koristiPročitajmo nekoliko veza kao rečenice: „Jedan restoran nudi jedno ili više jela, a jelo ne postoji bez restorana.” „Jedna narudžba sadrži jednu ili više stavki.” „Svaka stavka je od tačno jednog jela, a jedno jelo može biti u mnogo stavki.” „Narudžba može koristiti nijedan ili jedan kod popusta.”
Jedno pitanje vrijedi postaviti glasno: zašto Stavka ima samo količinu, a ne i cijenu? Ako restoran sutra poskupi Margaritu na 10,00 €, da li se mijenja i cijena već plaćene narudžbe N-1001? Ne smije! Zato u stvarnom sistemu stavka pamti cijenu u trenutku narudžbe (cijena_u_trenutku: int). Ovo je primjer kako domenski model otkriva poslovno pravilo koje niko nije izgovorio. Dopisujemo ga u backlog i u model.

Dijagram klasa je kalup. Ponekad je korisno nacrtati i kolače: stvarne objekte s vrijednostima u jednom trenutku. To je dijagram objekata. Ime objekta piše se podvučeno, u obliku ime: Klasa, a umjesto tipova stoje vrijednosti.
Narudžba N-1001, u trenutku kad je plaćena:
| Objekat | Vrijednosti |
|---|---|
| k01: Kupac | oznaka = "K-01" |
| r1: Restoran | oznaka = "R-1", naziv = "Picerija" |
| margarita: Jelo | naziv = "Pizza Margarita", cijena = 950 |
| sok: Jelo | naziv = "Sok", cijena = 250 |
| n1001: Narudzba | broj = "N-1001", udaljenost_km = 4.2, stanje = "NOVA" |
| s1: Stavka | kolicina = 2 → margarita |
| s2: Stavka | kolicina = 1 → sok |
Iz ovih vrijednosti operacije daju: s1.iznos() = 2 × 950 = 1900; s2.iznos() = 250; n1001.artikli() = 2150; n1001.dostava() = 300; n1001.ukupno() = 2450 centi = 24,50 €.
Dijagram objekata je odličan za provjeru modela: ako ne možete nacrtati stvaran primjer s vrijednostima, model ima rupu. Probajte, na primjer, nacrtati narudžbu bez stavki: višestrukost 1..* kaže da to nije dozvoljeno, i dijagram objekata to odmah pokaže.

n1001.ukupno()? 2450 centi, odnosno 24,50 €.Odgovori: tipka R ili klik na zamućeno polje.
Cilj je nacrtati domenski model DeliveryApp i napisati ga kao Python klase koje računaju cijenu narudžbe po pravilima PP-1 do PP-5. Fajlovi idu u modeli/ i kod/ repozitorija deliveryapp-projekat.
U modeli/klase.md napravite tabelu imenica iz svoje specifikacije UC-01 i UC-02 (poglavlje 5) s odlukom: klasa, atribut, operacija ili odbaciti, i razlogom. Najmanje 15 imenica.
modeli/klase.md prepišite domenski model iz 6.8 (blok mermaid).cijena_u_trenutku: int u klasu Stavka.kod/ kopirajte deliveryapp.py i model.py iz primjeri/deliveryapp/ (repozitorij udžbenika) ili ih prepišite.python model.pyOčekivani ispis:
N-1001 artikli 21,50 € dostava 3,00 € ukupno 24,50 €
sa kodom 22,35 €promijeni(novo) iz 6.3, koja koristi rječnik PRELAZI iz deliveryapp.py.model.py dodajte provjere:n.promijeni("PRIHVAĆENA")
n.promijeni("U_PRIPREMI")
try:
n.promijeni("OTKAZANA")
print("GREŠKA: otkazivanje u pripremi je prošlo")
except ValueError:
print("ispravno: otkazivanje u pripremi nije dozvoljeno (PP-6)")Nacrtajte (u Mermaidu kao classDiagram s imenima objekata, ili na papiru pa slikajte) dijagram objekata za N-1002 u trenutku isporuke: kupac, restoran, jelo, stavka, narudžba, dostavljač D-3 i plaćanje.
Dajte AI asistentu svoj Mermaid domenski model i zatražite: „Pročitaj svaku vezu kao rečenicu u oba smjera i reci mi koja ti zvuči sumnjivo za aplikaciju za dostavu hrane.” Za svaku primjedbu odlučite sami da li je opravdana. Zatražite i da iz modela napiše Python klase; pokrenite ih i uporedite rezultat s 24,50 €.
Rezultat koji se predaje: modeli/klase.md (analiza teksta i domenski model s dodacima), kod/model.py koji ispisuje 24,50 €, 22,35 €, poruku o PP-6 i 32,00 € za N-1002, te dijagram objekata za N-1002.
Rješenja su sakrivena – tipka R ili klik na okvir.
Šta je klasa, a šta objekat? a) Pizza Margarita na jelovniku R-1; b) Jelo; c) dostavljač D-3; d) Plaćanje; e) plaćanje karticom za N-1001 u iznosu 24,50 €.
Klase: b) Jelo, d) Plaćanje. Objekti: a) (instanca klase Jelo), c) (instanca klase Dostavljač), e) (instanca klase Plaćanje).
Napišite višestrukost i pročitajte vezu u oba smjera: a) Restoran – Narudžba; b) Kupac – Adresa (kupac mora imati bar jednu adresu da bi naručio, a može ih sačuvati više); c) Narudžba – Plaćanje.
a) Restoran "1" <-- "0.." Narudzba: jedan restoran ima nula ili više narudžbi; jedna narudžba je iz tačno jednog restorana. b) Kupac "1" --> "1.." Adresa: jedan kupac ima jednu ili više adresa; jedna adresa pripada jednom kupcu. c) Narudzba "1" --> "0..1" Placanje: jedna narudžba ima nijedno ili jedno plaćanje (prije naplate nijedno); plaćanje pripada tačno jednoj narudžbi.
a) Restoran i Jelo; b) Tim dostavljača i Dostavljač; c) PlacanjeKarticom i Placanje; d) Narudžba i Kupac.
a) kompozicija (jelo u DeliveryApp ne postoji bez restorana); b) agregacija (dostavljač postoji i bez tima); c) nasljeđivanje (plaćanje karticom je vrsta plaćanja); d) obična asocijacija (kupac nije dio narudžbe, niti obrnuto).
Napravite (na papiru) objekte za narudžbu N-1005: kupac K-05 iz Salatarnice R-3 naručuje 2 × Salata s piletinom (9,00 €) i 2 × Limunada (3,00 €), na 6,5 km, s kodom STUDENT10. Šta vraćaju artikli(), dostava() i ukupno() (u centima)?
artikli = 2 × 900 + 2 × 300 = 2400 centi (24,00 €). Dostava: 24,00 € < 30,00 €, a preko 3 km je 3,5 km, dakle četiri započeta kilometra: 200 + 4 × 50 = 400 centi (4,00 €). Popust: 10 % od 2400 = 240 centi (manje od 500). ukupno = 2400 − 240 + 400 = 2560 centi = 25,60 €.
Kupac K-01 je naručio N-1001 kad je Margarita koštala 9,50 €. Sedmicu kasnije restoran je poskupi na 10,00 € i kupac otvori istoriju narudžbi. Šta pokazuje model iz 6.8 bez dodatka iz koraka 2, a šta s dodatkom? Kako se to zove u projektovanju?
Bez dodatka stavka pokazuje na jelo, pa bi se ukupno ponovo izračunalo s novom cijenom: 2 × 10,00 + 2,50 + 3,00 = 25,50 €, što nije iznos koji je kupac platio (24,50 €). S atributom cijena_u_trenutku stavka pamti 950 centi, pa istorija pokazuje tačnih 24,50 €. Ovo je primjer da se istorijski podaci zamrzavaju u trenutku transakcije; to je poslovno pravilo koje je otkrio model, a ne korisnik.