PV168/Příklad objektového návrhu

Z FI WIKI
Přejít na: navigace, hledání

Ukážeme si příklad, jak udělat objektový návrh. Volím téma správy hřbitova, protože není v tomto semestru (jaro 2012) povoleno.

Entity

Nejdříve si zanalyzujeme, co jsou v tomto případě entity. Entita je ještě obecnější než "věc", je to cokoliv, co má vlastnosti, i když je to nehmotné, třeba výpůjčka knihy. V našem případě jsou entity hrob (Grave) a tělo (Body). Každé má nějaké vlastnosti, které si potřebujeme pamatovat:

Cemetery entities.png

U entit se vždy hodí mít nějaké umělé jednoznačné id. Praxe ukazuje, že identifikátory z reálného světa, třeba rodná čísla osob, uživatelská jména v operačních systémech, nebo VIN u automobilů, nejsou vhodná jako id, protože nejsou vždy jednoznačná a mohou se v čase měnit.

Fialové (P) v kolečku znamená "property", tedy že existuje dvojice set a get metod, například pro property "id" jsou to metody

 
    public int getId() {
        return id;
    }
 
    public void setId(int id) {
        this.id = id;
    }

Manager

Entitní třída se stará o držení dat pro danou entitu. Ale už není vhodné, aby se starala třeba o zobrazování dat na obrazovku, nebo o ukládání dat do databáze. Pro tyto operace je vhodné mít jinou třídu, a ještě lépe interface, který definuje možné operace s entitní třídou, a pak implementaci tohoto interface. Například:

Cemetery body manager.png

S entitami obvykle potřebujeme provádět tzv. CRUD operace - Create - Retrieve - Update - Delete. Proto interface BodyManager má metodu pro vytvoření nového záznamu o těle (typicky v databázi). Tato metoda má typicky signaturu

 
public interface BodyManager {
 
    Body createBody(Body body);
 
    //...
}

Je lepší, když parametrem této metody je instance třídy Body, než její položky, např.:

 
    Body createBody(int id, String name, Calendar born, Calendar died, boolean vampire);

protože v případě, že přidáme do třídy Body další položku, už nemusíme měnit metodu createBody(). V programu pak vytvoření nového záznamu vypadá nějak takto:

 
   Body body = new Body();
   body.setName("Vlad Dracula");
   body.setBorn(new GregorianCalendar(1431, Calendar.JANUARY,1));
   body.setDied(new GregorianCalendar(1476, Calendar.DECEMBER,20));
   body.setVampire(true);
        
   BodyManager bodyManager = getBodyManager();
   bodyManager.createBody(body);

Interface BodyManager je představitelem návrhového vzoru Data Access Object.

Vztah mezi entitami

Nyní si rozebereme vztah mezi těmito entitami. Je to vztah 1:N, protože v jednom hrobě může být více těl, ale jedno tělo je nanejvýš v jednom hrobě. (Další možnost vztahu je N:M, třeba vztah mezi vyučovanými předměty a studenty, neboť jeden student může studovat více předmětů a jeden předmět je studován více studenty.)

Jedna možnost, jak vyjádřit vztah 1:N, je zavést do třídy Grave vlastnost bodies typu Collection<Body>, jak je vidět na tomto diagramu:   Cemetery 1toN.png

Ale toto řešení má svoje nevýhody. Zejména paměťovou náročnost, protože ke každé instanci třídy Grave v paměti bychom museli mít v paměti i příslušné instance třídy Body. V tomto případě to není tak hrozné, ale představte si třeba model obchodu, který má oddělení, a každé oddělení má odkazy na nadřízená a podřízená oddělení a na výrobky v oddělení, a výrobek má odkaz na oddělení, do kterých patří. Pak musíme při načtení jedné instance výrobku do paměti kvůli tranzitivním závislostem načíst do paměti úplně všechno, což nemusí být únosné.

Z hlediska využité paměti je lepší řešení, kdy zavedeme správce tohoto vztahu. Zavedeme tedy interface CemeteryManager:

Cemetery full.png

Tento interface nám poskytuje metody pro vazbu mezi hroby a těly.

Tím je objektový návrh hotový.

Časté chyby

Častá chyba návrhu je, že manažer entitní třídy, místo aby poskytoval CRUD operace nad nějakým úložištěm dat, drží všechno v paměti, a obsahuje dvojici metod typu "load()" a "store()". Takový návrh není škálovatelný.

Další častá chyba je, že metody manažerů entit mají místo jednoho argumentu typu entitní třídy mnoho argumentů odpovídajících položkám entitní třídy. Pak je s každou změnou entitní třídy nutné měnit i většinu metod manažeru.

Dalším častým prohřeškem je špatné oddělení odpovědnosti tříd, kdy entitní třída nejenže drží data, ale poskytuje i metody např. "saveToDatabase()" nebo "showOnScreen()". Tím vzniká zbytečná závislost této třídy na jiných třídách. Pokud se pak rozhodneme změnit způsob uložení dat nebo uživatelské rozhraní, je to mnohem těžší.