# Marker

Občas sa stane, že pre správne fungovanie kódu si potrebujeme o triedach uložiť jednoduché meta dáta. Jedno zo základných riešení tohto problému je Marker.

Marker je prázdne rozhranie bez tela, tiež nazývané ako značka. Trieda, ktorú ním potrebujeme označiť, ho len zahrnie do svojho zoznamu rozhraní. Potom neskôr v kóde uź len budeme kontrolovať, či trieda je inštancia konkrétneho Markera, alebo nie.

Moderné kódy však na poznačenie si metadát používajú anotácie, ktoré majú najväčšiu výhodu v tom, že sú flexibilnejšie a dokážu niesť aj viac informácií, ako len jednoduchú značku. Keďže však vyžadujú použitie reflexie, sú aj "drahšie" na zdroje ako jednoduché rozhranie.

# Modelová situácia

Použitie markera si ukážeme na príklade z prostredia e-shopov. Všetky naše položky v inventári, ktoré predávame, môžeme reprezentovať nejakou všeobecnou abstraktnou triedou Item. Z nej potom dedia mnohé konkrétnejšie - napr. Book, Computer či Printer. Avšak okrem normálnych položiek máme občas aj nejaké špeciálne. Typická ukážka je zľavový kupón - Voucher. Jedna z odlišností je to, že kupónov môžeme predať toľko, koľko chceme - inými slovami, ich množstvo je neobmedzené a vždy budú dostupné. Preto je vhodné si túto informáciu uchovať, aby sme ju vedeli vhodne použiť. Je viacero možností, ako túto vlastnosť pomenovať, my zvolíme "vždy dostupné" - AlwaysAvailable:

public interface AlwaysAvailable {
    // Marker interface
}

Tú následne implementujeme v triede Voucher:

public class Voucher extends Item implements AlwaysAvailable {
    // Rest of the class omitted
}

Ak by sme mali nejakú metódu, ktorá kontroluje dostupnosť danej položky, mohla by vyzerať nasledovne:

public boolean isAvailable(Item item) {
    if (item instanceof AlwaysAvailable) {
        return true;
    }

    // Performance intensive operation
    return inventory.checkAvailability(item);
}

Ďalšiou dobrou ukážkou na rovnakom prípade je filtrovanie. Povedzme, že musíme vybrať zo zoznamu položiek tie, ktoré chceme zobraziť v reklame. Bolo nám povedané, aby sme tam nezahrnuli príslušenstvá (Accessory) ani vulgárne/neslušné (Explicit) produkty. Ak takéto markeri aplikujeme na príslušné triedy (napr. položka typu PhoneCase bude označená ako Accessory), odfiltrovať ich zo zoznamu môžeme nasledujúcim kódom:

public List<Item> filterItemsForAdvertisement(List<Item> items) {
    return items.stream()
            .filter(item -> ! (item instanceof Accessory))
            .filter(item -> ! (item instanceof Explicit))
            .collect(Collectors.toList());
}

Ak by sme chceli riešenie hore zovšeobecniť, mohli by sme metódu zapísať nasledovne:

public List<Item> filterOutMatching(List<Item> items, List<Class<?>> markers) {
    return items.stream()
            .filter(item -> markers.stream().noneMatch(marker -> marker.isInstance(item)))
            .collect(Collectors.toList());
}

Tu máme možnosť odfiltrovať akúkoľvek položku, ktorá je označená aspoň jedným z poskytnutých markerov. Volať by sme ju mohli nasledovne:

List<Item> filtered = filterOutMatching(items, Arrays.asList(Accessory.class, Explicit.class));

# Best practices

  • Snažte sa Marker používať len s mierou a neprehnať to
  • Väčšinou je lepšie použiť metódy s návratovou hodnotou boolean
  • Treba zvážiť, či použitie anotácií nie je pre vašu situáciu vhodnejšie

# Úlohy

Vzhľadom na jednoduchosť tohto návrhového vzoru neuvádzame žiadne ďalšie úlohy.

# Diskusia