🏗️ Šta su Design Patterns?
Za razliku od pravih biblioteka koda koje instališete (poput jQuery i Bootstrap-a), Dizajn Obrasci (Design Patterns) su opisana i teoretski dokazana rješenja za softverske probleme koji se stalno ponavljaju. Nastali su na osnovu čuvene knjige "Design Patterns: Elements of Reusable Object-Oriented Software" iz 1994. godine (tzv. Gang of Four).
Javne institucije poput ministarstava, općina i poreskih uprava obično razvijaju gigantske platforme koje istovremeno moraju rukovati porezima, radnicima, korisnicima i budžetima. Dizajn obrasci su univerzalni jezik koji omogućava arhitektama da kažu programerima: "Koristit ćemo Facade za komunikaciju sa sudovima", bez potrebe za objašnjavanjem implementacije 15 minuta.
📊 Klasifikacija Obrazaca
| Kategorija | Glavna Svrha | Tipični Patterni |
|---|---|---|
| Creational (Kreativni) | Sigurno kreiranje i inicijalizacija (instanciranje) složenih objekata. | Singleton, Factory Method, Builder |
| Structural (Strukturni) | Povezivanje različitih klasa u veću, pametniju strukturu (Posebno kod Legacy koda). | Adapter, Decorator, Facade |
| Behavioral (Ponašajni) | Komunikacija, delegacija zadataka i raspodjela odgovornosti između objekata. | Observer, Strategy, Command |
✨ Creational (Kreativni) Patterni
1. Singleton Pattern - Vladina Konfiguracija
Zadatak: Aplikacija za izdavanje pasoša učitava cjenovnik taksi iz strogo povjerljive baze. Operacija učitavanja baze traje 2 sekunde.
Ako za svakog korisnika koji otvori portal instanciramo `new Cjenovnik()`, portal će se usporiti do tačke neupotrebljivosti.
Rješenje Singleton: Garantuje da u RAM memoriji cijelog servera postoji isključivo JEDNA instanca objekta koja se dijeli svim korisnicima.
public sealed class DrzavniCjenovnik
{
// C# 'Lazy' garantuje Thread-Safety apsolutno savršeno (štiti od miješanja podataka)
private static readonly Lazy<DrzavniCjenovnik> _instance =
new Lazy<DrzavniCjenovnik>(() => new DrzavniCjenovnik());
public decimal TaksaZaPasos { get; private set; }
// 1. Konstruktor MORA biti privatan. Zabrana komande 'new'!
private DrzavniCjenovnik()
{
Console.WriteLine("Skupo očitavanje baze podataka... (Samo JEDNOM se dešava)");
TaksaZaPasos = 50.00m;
}
// 2. Globalna tačka pristupa instanci
public static DrzavniCjenovnik Instance => _instance.Value;
}
// UPOTREBA u Kontroleru (milion korisnika izvršava ovaj kod, svi dijele isti objekat u nanosekundi)
var cijena = DrzavniCjenovnik.Instance.TaksaZaPasos;
2. Factory Method Pattern - Dispečer "Šaltera"
Zadatak: Korisnik traži Rodni List ili Krivično Uvjerenje. Svaki dokument se povlači iz različite baze (MUP vs Sud). Ne želimo if-else špagete svugdje u kodu.
Rješenje Factory: Delegate kreiranje objekta na centralnu "Fabriku".
public interface IZvanicniDokument { void Generisi(string jmbg); }
public class RodniList : IZvanicniDokument {
public void Generisi(string jmbg) => Console.WriteLine($"IDDEEA: Rodni list za {jmbg}");
}
public class KrivicnoUvjerenje : IZvanicniDokument {
public void Generisi(string jmbg) => Console.WriteLine($"SUD: Provjera dosijea {jmbg}");
}
// FABRIKA: Mozak operacije
public static class DokumentFactory
{
public static IZvanicniDokument Kreiraj(string vrsta)
{
return vrsta.ToUpper() switch
{
"RODNI_LIST" => new RodniList(),
"KRIVICNO" => new KrivicnoUvjerenje(),
_ => throw new ArgumentException("Dokument ne postoji u registru.")
};
}
}
// UPOTREBA:
var dokument = DokumentFactory.Kreiraj("RODNI_LIST");
dokument.Generisi("1208990150011"); // Ovdje programer više uopšte ne razmišlja "je li ovo MUP ili Sud"!
🧱 Structural (Strukturni) Patterni
1. Adapter Pattern - Integracija starog Mainframe-a
Kada pravite moderni API koji očekuje JSON, a prisiljeni ste čitati podatke iz baze Penzionog fonda (PIO/MIO) iz 1999. godine koja komunicira preko XML fajlova. Adapter djeluje kao "prevodilac struje" sa 110V na 220V.
Interni poziv: StariXml() -> Prevod -> JSON
2. Decorator Pattern - Ljuska oko jezgre (Babuške)
Kada želite nekoj funkciji (npr. Čitanje baze korisnika) dodati Logiranje i Auth Provjeru, bez da uđete u samu klasu i poremetite stari kod. Obmotavate staru klasu "ljuskama".
public interface IGradjanskiServis { string DohvatiPodatke(string jmbg); }
// 1. Jezgra: Obični read iz baze (NEMA SIGURNOSTI U SEBI!)
public class OsnovniServis : IGradjanskiServis {
public string DohvatiPodatke(string jmbg) => "{ ime: 'Sanja' }";
}
// 2. Decorator: Dodaje LOGOVANJE zbog Zakona o zaštiti podataka
public class AuditLogDecorator : IGradjanskiServis
{
private readonly IGradjanskiServis _inner; // Drži referencu na unutrašnju babušku
public AuditLogDecorator(IGradjanskiServis inner) { _inner = inner; }
public string DohvatiPodatke(string jmbg) {
Console.WriteLine($"[LOG]: Revizorski trag - pristupljeno dosijeu {jmbg}");
return _inner.DohvatiPodatke(jmbg); // Tek onda proslijedi zahtjev dublje!
}
}
// UPOTREBA: Inception (Pakovanje ljuski)
IGradjanskiServis servis = new OsnovniServis();
servis = new AuditLogDecorator(servis); // Omotali smo ga zaštitnom ljuskom
// Rezultat: Sistem ispisujemo LOG poruku, pa tek VRAĆA podatke iz baze.
string ispis = servis.DohvatiPodatke("111");
3. Facade Pattern - Pojednostavljenje kaosa
Za osnivanje D.O.O firme, potreban je ID broj, registracija u Sudu i fiskalna kasa. Korisnik (ili sistem za korisnike) kroz Facade (Fasadu) dobija JEDNO jednostavno dugme `KreirajFirmu()`, a Fasada u pozadini zove sve institucije.
// 1. Desetine teških sistema (Haos u pozadini)
class PoreskaUprava { public void GenerisiID() { /* ... */ } }
class SudskiRegistar { public void UpisiSudskoRjesenje() { /* ... */ } }
class Penzijsko { public void PrijaviRadnika() { /* ... */ } }
// 2. FASADA (Štit od haosa)
public class RegistracijaFirmeFacade
{
private PoreskaUprava _p = new();
private SudskiRegistar _s = new();
private Penzijsko _penz = new();
// Jednostavni "Ugovor" koji krijemo od korisnika
public void OtvoriFirmu(string naziv)
{
_s.UpisiSudskoRjesenje();
_p.GenerisiID();
_penz.PrijaviRadnika();
Console.WriteLine($"Firma '{naziv}' je potpuno otvorena u svim registrima!");
}
}
// UPOTREBA NA WEB PORTALU: Programer zove fasadu u samo 2 linije! Štedi sate nerviranja.
var eSalter = new RegistracijaFirmeFacade();
eSalter.OtvoriFirmu("SuperSoft d.o.o.");
🚦 Behavioral (Ponašajni) Patterni
1. Strategy Pattern - Plaćanje komunalija
Ako općina uvodi takse, korisnik može platiti putem Kartice, E-bankinga ili Uplatnice (PDF). Umjesto switch naredbe od 100 linija u kojoj je zapisano kako obračunati svaku metodu, mi metod ubacimo (injektujemo) kao posebnu "Strategiju".
public interface INacinPlacanja { void Plati(decimal iznos); }
public class PlacanjeKarticom : INacinPlacanja {
public void Plati(decimal iznos) => Console.WriteLine($"Plaćeno {iznos}KM preko Stripe-a.");
}
public class PlacanjeUplatnicom : INacinPlacanja {
public void Plati(decimal iznos) => Console.WriteLine($"Kreirana PDF uplatnica na {iznos}KM.");
}
// KLIJENT (Kontroler). On apsolutno ne poznaje matematiku naplate, samo izvršava ono što mu se prosljedi.
public class Blagajna
{
public void Izvrsi(decimal iznos, INacinPlacanja strategija)
{
strategija.Plati(iznos); // Izvršava se ona strategija koja je dodijeljena dinamički!
}
}
2. Command Pattern - Praćenje i Poništavanje Rješenja (Undo)
Kada ministar donese odluku o izmjeni budžeta, on to radi klikom miša. Da li on smije to poništiti (UNDO) ako je napravio grešku? Command pattern svaku akciju pretvara u Puni Objekat koji se može sačuvati u bazu, zakazati (Queue) ili obrisati (Undo) iz historije!
// Ugovor koji svaka komanda u sistemu mora poštovati
public interface IKomanda
{
void Izvrsi();
void Ponisti(); // Suho zlato!
}
// KONKRETNA KOMANDA: Prebacivanje Novca
public class PrebaciBudzetKomanda : IKomanda
{
private decimal _iznos;
public PrebaciBudzetKomanda(decimal iznos) { _iznos = iznos; }
public void Izvrsi() => Console.WriteLine($"IZVRŠENO: Prebačeno {_iznos} KM u rezervu Vlade.");
public void Ponisti() => Console.WriteLine($"PONIŠTENO (UNDO): Vraćeno {_iznos} KM iz rezerve nazad trezoru.");
}
// UPOTREBA:
IKomanda komandaRjesenja = new PrebaciBudzetKomanda(500000m);
komandaRjesenja.Izvrsi();
// Sutra dan sud proglasi nezakonitost? Nema problema:
komandaRjesenja.Ponisti();