Laboration 4 - Några designmönster
Designmönster beskriver beprövade lösningar som kan användas i
flera olika, men liknande, sammanhang inom objektorienterad programmering.
Med designmönster i sin "verktygslåda" kan designproblem lösas snabbare
och säkrare än utan. Vissa mönster är enkla och lätta att lära sig medan
andra är lite komplicerade.
När man ska lära sig att använda ett nytt mönster praktiskt så är det
en god idé att programmera upp mönstret på ett litet "leksaksexempel"
för att verkligen förstå hur det är tänkt att fungera.
I den här labben ska ett par vanliga och populära mönster studeras
genom att små exempel programmeras.
Mål
- Att förstå uppbyggnaden av objekt enligt mönstret Composite och särskilt hur
operationer på sådana objekt fungerar.
- Att förstå Factory-teknik och hur man m.h.a. paket och modifierare kan åstadkomma
en factory-metod som skapar objekt av för användaren oåtkomliga subklasser.
Mönstret Composite
Idén med mönstret Composite är att grupper av objekt ordnade hierarkiskt i en
trädstruktur ska kunna behandlas på samma sätt som enstaka objekt.
En operation på det sammansatta objektet ska utföras på objektets alla
delar.
Ett sammansatt objekt (composite object) innehåller andra objekt
som själva kan vara sammansatta eller enstaka.
Ett typiskt exempel på Composite är en filkatalog där en katalog kan
innehålla både filer och andra kataloger, som i sin tur kan innehålla
både filer och andra kataloger o.s.v. i många nivåer. Filerna är löv i trädet.
Ett annat exempel är en bild som
byggs upp av grafiska primitiver (linjestycken, ovaler, rektanglar m.m)
och sammansättningar av grafiska primitiver där en sammansättnng kan
innehålla både andra sammansättningar och primitiva objekt.
Här finns en beskrivning av Composite:
www.dofactory.com/Patterns/PatternComposite.asp
Kommentar till mönsterbeskrivningen ovan: Man kan välja att
i den gemensamma superklassen Component endast ha
de attribut och metoder som är meningsfulla i både Leaf och
Composite .
Då tas alltså add() , remove() och
getChild() bort från Component och implementeras
bara i Composite där de går att utföra.
Om t.ex. add() finns i Component så finns den
också i Leaf p.g.a. arvet men add() är meningslös
och t.o.m. otillåten i Leaf !
Uppgift 1 – Resväska som Composite
En resväska kan packas enligt mönstret Composite.
Implementera de klasser som behövs för en resväska enligt Composite.
Den klass som kallas Component i mönstrets vanliga beskrivning är
en abstrakt klass från vilken klasserna för de enskilda prylarna
och klassen eller klasserna för sammansättningarna ärver.
Välj själva om ni vill göra olika klasser för de olika sorternas
prylar eller använda en klass med ett String-attribut som namnger
prylen. Det senare är absolut enklast!
Även när det gäller behållarklassen (Composite i mönstret) kan man välja att ha en enda
klass för alla
eller en för Necessär, en för Påse, en för Väska o.s.v. Det blir förstås
enklast att ha en enda behållarklass också, med en String-variabel som specificerar
användningen.
Minst tio saker ska packas och minst tre olika nivåer av sammansättningar
ska användas. T.ex. kan hårspännen ligga i en påse som ligger i en necessär
tillsammans med tvål och schampoo och necessären kan ligga i resväskan
bredvid större klädesplagg.
Se UML-diagrammet!
Låt alla komponenter (klassen Component i mönstret) ha ett namn och en
vikt som instansvariabler. Dessa ges värden när objekten skapas genom en vanlig
konstruktor.
Låt alla komponenter ha operationerna getWeight() och
toString() . getWeight() för en liten pryl (löv)
returnerar endast prylens vikt medan getWeight() för en behållare
returnerar hela behållarens vikt (egenvikten plus summan av vikterna av allt
den innehåller). toString() för en liten pryl returnerar namnet
medan toString() för en behållare ska returnera en String
med behållarens namn följt av namnen på alla saker som finns i behållaren.
Skriv ett testprogram som bygger upp en resväska.
Resväskans totala vikt ska beräknas i ett metodanrop, t.ex.
suitcase.getWeight() och skrivas ut (ingen utskrift i metoden förstås).
Tänk på att själva väskan och andra behållare har egen vikt utöver vikten för innehållet!
Skriv också ut "hela resväskan". Det ska ge en lista över innehållet. Tag sen bort några
komponenter ur innehållet. Skriv ut vikt och innehåll igen och kolla att det är rätt!
Observera att inga explicit rekursiva metoder ska skrivas.
Det är mönstrets struktur som ser till att hela innehållet
gås igenom då en metod anropas för något objekt i strukturen.
Det är tillåtet att programmera ett annat exempel på mönstret Composite om
ni kommer på något passande. Det måste vara minst tre nivåer och minst
tio objekt totalt.
getWeight() och toString() ska implementeras enligt mönstret.
Det innebär att varje anrop på ett Composite-objekt ska gå igenom alla objektets barn.
Factory-teknik
Det finns flera designmönster som innehåller ordet Factory.
Gemensamt för dem är att man inte skapar objekt på det vanliga sättet
med new utan genom ett metodanrop. Metoderna ses som "fabriker"
där objekt skapas och vanliga namn på metoderna är getInstance()
eller create() . Typen för det skapade objektet bestäms i vissa fall
i metoden utom räckhåll för klienten (användaren).
Inuti metoderna används förstås new men klienten, den som använder
klassen, har inte direkt tillgång till new.
Vanliga skäl för den här tekniken är att användaren ska slippa bry sig
om objektets typ eller att det säkrare blir rätt typ om metoden väljer
och inte användaren eller att klassen själv skall ha full kontroll över
vilka objekt som skapas.
Uppgift 2 – Human factory
Implementera en abstract klass Human med två konkreta subklasser
Man och Woman . I Human ska en
factory-metod finnas :
public static Human create (String pnr) {
// metodkropp
}
Vi förutsätter att parametern är ett riktigt personnummer om tio siffror
och ett streck före de fyra sista (ni behöver inte kolla det).
Om näst sista siffran är udda så ska en instans av
Man returneras, om den är jämn returneras ett objekt av
Woman.
Skriv ett testprogram som skapar objekt av Man och
Woman med hjälp av metoden ovan.
Se till att det inte går att skapa objekt av Man
och Woman på annat sätt än genom metoden ovan. Det ska
alltså inte gå att kompilera new Man(...) eller
new Woman(...) i testprogrammet. Det får inte heller gå
att skapa en anonym subklass till Human .
Tips: Gör ett paket human som innehåller
Human och dess subklasser. Paketet läggs på en
underkatalog med samma namn. Med hjälp av
lämpliga modifierare för synlighet (public, private m.fl.) kan man
åstadkomma den begärda strukturen. Lägg testprogrammet på
katalognivån ovanför paketet. Se också till att testprogrammet
på något sätt bekräftar att objekt av rätt typ skapats, t.ex.
genom en metod toString() som skriver ut information
om objekten. Exempel:
:
Human anna = Human.create("Anna", "xxxxxx-012x");
Human magnus = Human.create("Magnus","xxxxxx-011x");
System.out.println(anna);
System.out.println(magnus);
kan ge utskriften
Jag är kvinna och heter Anna
Jag är man och heter Magnus
Redovisning och krav på programmen
- Composite-mönstret ska implementeras med minst två klasser.
UML-diagrammet som visas har tre men lösningar med två klasser godkänns.
- Demonstrera körning av ett testprogram där minst tio saker i minst tre
nivåer bygger upp en Composite. Skriv ut totalvikt och innehåll.
Tag bort några saker ut väskan. Skriv ut totalvikt och innehåll igen.
- UML-klassdiagram behövs inte om det följer det som länkas till ovan.
Ni får ha andra namn på era klasser utan att behöva rita om.
Om ni har färre eller fler klasser så rita eget!
- Rita ett objektdiagram över de objekt som
testprogrammet skapar. Diagrammet behöver inte följa UML-standard men
tydligt visa vilka objekt som finns. Rita diagrammet som ett träd!
- Demonstrera en körning av Human-factory-programmet där en man och
en kvinna skapas.
- Demonstrera en annan version av programmet där
man försöker med
new Man(...) och new Woman(...)
men kompilering misslyckas.
Extrauppgift - Mönstret Iterator
Syftet med mönstret är att iterera över eller gå igenom alla element i en
sammansatt struktur utan man behöver befatta sig med den underliggande
strukuren. Att använda en iterator på en lista är naturligt och
implementationen av en listiterator är ofta enkel.
Även för mer komplicerade strukturer har man god nytta av iteratorer.
Composite-sammansättningar och andra trädstrukturer kan mycket väl förses
med iteratorer. Iteratorn "levererar" elementen i sekvens utan att man
behöver veta hur strukturen är uppbyggd.
"Man" är här den som använder iteratorn. Den som programmerar iteratorn måste
förstås känna till strukturen. Naturliga val för genomgångar av
en trädstruktur är t.ex. pre-, in- och post-order eller den ordning
som fås vid s.k. bredden-först-sökning.
Mer om iteratormönstret finns här:
www.dofactory.com/Patterns/PatternIterator.asp
Läs även i Java-API:n om
Iterable ,
Iterator samt följande tutorial:
The for statement.
Bredden-först-iterator
Besök först roten, därefter rotens alla barn, sen rotens barnbarn o.s.v.
Låt iteratorn implementera interfacet som länkas till ovan och som finns i paketet
java.util . Det visas här nedanför också.
E står här för typen av element som levereras av iteratorn,
kan vara t.ex. Component .
public interface Iterator<E> {
E next(); // gå fram ett steg och returnera nästa element
boolean hasNext(); // returnera true om det finns fler element att besöka
void remove(); // krävs ej i labben, implementeras tom
}
Låt Composite-klassen implementera Iterable (läs ovan) så att den har
metoden iterator() . Definiera iterator() så att den
returnerar ett iterator-objekt. Med detta upplägg blir det möjligt att gå igenom
Composite-strukturen med en for-each-sats såväl som med iteratorns metoder.
Visa båda i testprogrammet!
Djupet-först-iterator
Implementera även en djupet-först-iterator. Definiera ytterligare en iterator-metod i
Composite som returnerar denna iterator eller gör det möjligt att byta iteratortyp
genom att kommentera bort och avkommentera lämpliga programdelar.
Redovisning
Testning av de nya iteratorerna går bra att göra i samma program som
demonstrerar Composite-strukuren då labbens första del redovisas.
Bestäm själva hur ni ska göra för att visa att båda iteratorerna fungerar!
|