MODUL 1 - LEKCIJA 6

Design Patterns (Dizajn Obrasci) Architecting

Provjerena softverska rješenja za masovne sisteme. Naučite implementaciju standardiziranih obrazaca kroz primjere iz portala e-Uprave, sudskih registara i općinskih usluga.

⏱️ Trajanje: ~60 min 📚 Nivo: Napredni 🎯 Koncepti: Creational, Structural, Behavioral Patterns

🏗️ Š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.

📜 Singleton - Zvanični Cjenovnik (Moderni C# Lazy T)
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".

📜 Factory Method - Fabrika dokumenata
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.

Client (Novi Portal)
Očekuje lagani JSON API.
Adapter
Implementuje Client Interface.
Interni poziv: StariXml() -> Prevod -> JSON
Legacy Sistem
Vraća glomazni XML format. Ne smije se modifikovati.

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".

📜 Decorator - Sigurnosne ljuske
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.

📜 Facade - Objedinjeni Šalter e-Firme
// 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".

📜 Strategy - Odabir Naplate
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!

📜 Command Pattern - Naredbe Vlade sa opcijom Poništavanja
// 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();