S využitím JDBC implementujte jednoduchý katalog produktů. V archivu pa165.tar.gz naleznete rozhraní Product
, Group
a Catalog
. Implementujte třídy cz.muni.fi.pa165.catalog.jdbc.ProductImpl
, cz.muni.fi.pa165.catalog.jdbc.GroupImpl
a cz.muni.fi.pa165.catalog.jdbc.CatalogImpl
, které budou tato rozhraní implementovat. Třída cz.muni.fi.pa165.catalog.jdbc.CatalogImpl
bude mít bezparametrický konstruktor a URL databáze získá jako system property 'cz.muni.fi.pa165.catalog.jdbcUrl', jméno uživatele bude v property 'cz.muni.fi.pa165.catalog.jdbcUser' a heslo bude v property 'cz.muni.fi.pa165.catalog.jdbcPassword' (viz System.getProperty(String). Při vytváření instance katalogu nezapomeňte vytvořit tabulky v databázi! Jako JDBC driver použijte org.hsqldb.jdbcDriver.
Zadání bude ještě trochu upřesněno!
Q: Do kdy má být úloha odevzdána?
A: Na vypracování máte čtrnáct dní, tj. pro skupiny 3 (pondělí 16:00) a 4 (pondělí 18:00) je deadline 25.10. a pro skupiny 1 (středa 14:00) a 2 (středa 16:00) je deadline 27.10. Ale začněte úlohu řešit už nyní, abyste se mohli na případné nejasnosti zeptat ve cvičení 18.10. resp 20.10. Na těchto cvičeních Vám také mohou být uděleny rady a pokyny, jak danou úlohu správně řešit.
Q: Jak si mám vyložit upozornění "zadání bude ještě trochu upřesněno", které je už od pondělí na konci zadání?
A: Pokud někdo upozorní na nejasnost, bude zadání upřesněno.
Q: Trochu mě zaráží použití třídy List
jako návratový typ u metod GetChildGroups
a getProducts
. Raději bych použil Map
(kvůli použití oid jako klíče).
A: Máte pravdu, že by se jako návratový typ dal teoreticky použít Map
. Ale většinou se to tak nedělá. Vhodnější je použít obecnou kolekci (Collection
) a v případě potřeby do rozhraní (v našem případě Group
) doplnit metodu, která nalezne a vrátí objekt s daným klíčem. Práce s objekty typu Map
je obecně komplikovanější (zkuste porovnat kód nutný k vypsání všech objektů v kolekci typu List
a v kolekci typu Map
). Když budete chtít se seznamem objektů dále pracovat, nebo jej předat dál, je mnohem pravděpodobnější, že se vám bude hodit typ Collection
(případně List
nebo Set
). Pravidlo číslo jedna, kterého byste se vždy měli držet, zní: "Udělej to tak nejjednodušeji, jak jen to jde."
Poznámka: V našem případě jsme místo typu Collection
použili typ List
, protože jako druhou úlohu budete možná řešit nějaké rozhraní pro tento katalog, a potom se vám s uspořádanou kolekcí bude pracovat lépe, než s kolekcí neuspořádanou.
Q: Vy asi budete testovat správnost úloh automaticky podle daných rozhraní, tak nevím, jestli to mohu/nemohu pro sebe změnit.
A: Źádné změny zadaných rozhraní provádět nemůžete!
Q: Jakým způsobem se má zakládat úplně první skupina (která nemá žádnou parentGroup
)?
A: Založíte ji v okamžiku vytvoření katalogu.
Q: Co vracet v metodě getRootGroup()
když bude kořenových skupin více? Mám při zakládání kořenové skupiny (viz předchozí bod) hlídat aby se to nestalo?
A: Kořenová skupina bude vždy pouze jedna.
Q: Co když zavolám metodu newGroup(...)
nebo newProduct(...)
s parametrem parentGroup
s hodnotou null
.
A: Metoda vyvolá výjimku NullPointerException
.
Q: Metody getChildGroups()
a getProducts()
jsou v GroupImpl
vzhledem k absenci dalších metod evidentně určeny k tomu, aby vracely originální seznamy patřičných objektů a umožnily tak úpravy a rušení svázaných objektů. Nebylo by v rámci zapouzdření lepší vracet pouze read-only List (Collections.unmodifiableList(...)
) a přidat do rozhraní Group
metody pro jejich změny?
A: Samozřejmě se předpokládá, že vrácený seznam bude read-only. Přidávání podskupin a produktů řeší metody newGroup(...)
a newProduct(...)
, odebírání a přesouvání skupin a produktů není pro jednoduchost vyžadováno. Úpravy objektů se řeší přímým voláním metod daného objektu. (Těmto otázkám se budeme věnovat ve cvičení 18.10. resp 20.10.)
Q: Jakým způsobem má fungovat například metoda CatalogImpl.newProduct(Group, String, double)
? Na vstupu mám objekt implementující rozhraní Group, do kterého mám přidat nově vytvořený produkt, ale rozhraní Group nemá žádné metody, které by to umožňovaly. A změnit ho nesmím. Analogická situace je u metody newGroup()
.
A: Rozhraní Group neposkytuje metody pro přidávání a odebíraní podskupin a produktů zcela úmyslně kvůli dodržení principu zapouzdření. Možností, jak Váš problém vyřešit je více, ale standardní řešení vypadá zhruba takto:
public void newProduct(Group parentGroup,...) { if (!parentGroup instanceof GroupImpl) { throw new IllegalArgumentException("Parameter parentGroup is not from this catalog."); } else { GroupImpl parent = (GroupImpl) parentGroup; ... } }
Q: To je sice řešení, které je použitelné ve školní úloze o 10 třídách, ale obecně to vhodný přístup určitě není. Dopisovat "if" blok pri každém přidání třídy implementující rozhraní Group je přece nesmysl. Na metodách např. Group.addSubGroup(Group)
a Group.addProduct(Product)
nevidím nic, co by narušovalo princip zapouzdření.
A: Dekompozice byla schválně udělaná tak, aby za všechny změny ve struktuře katalogu bylo odpovědné rozhraní Catalog (jde o standardní vzor). Přidáním veřejné metody Group.addSubGroup(Group) a Group.addProduct(Product) do rozhraní Group dáváte uživateli možnost zcela obejít metodu newProduct a newGroup u rozhraní Catalog a přidat tam úplně cizí objekt, který si vytvoří někde bokem. Navíc zbytečně zaplevelujete veřejné rozhraní metodami, které by buď uživatel tohoto rozhraní neměl vůbec používat (pak tam nemají co dělat), a nebo nutíte implemenntátora dodržet tato rozhraní a implementovat příslušné metody, i když by chtěl implementaci provést zcela odlišným způsobem. Uvědomte si, že byste u veřejného rozhraní neměl nic vědět o vnitřní struktuře objektu, který může být např. pouhou skořápkou nebo proxy objektem a který tak vůbec uvnitř seznam produktů a skupin vůbec nemusí mít!
Co se týče Vaší připomínky k přidávání příkazu if, pak podotýkám, že ten by tam musel být stejně, neboť byste stejně musel kvůli zajištění konzistence ověřit, jestli Vám tam někdo nepředává jako parametr skupinu z jiného katalogu. A další příkaz if byste musel přidat do Group.addSubGroup(Group) a Group.addProduct(Product), abyste ošetřil případ, kdy by Vám tam někdo opět dával objekt z cizího katalogu.
Vytvořte www rozhraní pro katalog produktů z první úlohy. Z pedagogických důvodů nepoužívejte Struts ani jiný rámec, MVC pattern použít můžete (je silně doporučen).
Pro řešení druhé úlohy upravte svoji implementaci katalogu z úlohy první tak, aby nenavazovala spojení s databází ve vlastní režii (podle nastavení systémové property 'cz.muni.fi.pa165.catalog.jdbcUrl'), ale využila zdroje dat (DataSource) poskytovaného webovým kontejnerem prostřednictvím JNDI (viz informace poskytnuté na cvičení). Tento zdroj bude mít název 'jdbc/CatalogDB'. Jak nakonfigurovat datové zdroje v prostředí kontejneru Tomcat naleznete na adrese http://jakarta.apache.org/tomcat/tomcat-5.0-doc/jndi-datasource-examples-howto.html.