Kurt Nørmark ©
Department of Computer Science, Aalborg University, Denmark
September 2001
Abstract Previous lecture Index References Contents | Genbrug og objekt-orienteret programmering er knyttet tæt til hinanden. Det har imidlertid vist sig, at genbrug på enkelt-klasse niveau ikke har givet så stort udbyttet, som man har drømt om. Genbrug af mønstre mellem klasser, såkaldte design mønstre eller mini-arkitekturer, er en meget lovende udvikling. I denne lektion vil vi introducerer de basale ideer omkring 'design patterns', og vi vil se på en række udvalgte eksempler på mønstre |
Genbrug |
Genbrug Slide Note Contents Index References | Genbrug af programmer, eller dele af programmer, er en gammel drøm. Her ser vi overordnet på et antal forudsætninger for at genbrug vil kunne lykkes |
|
|
Genbrug i forhold til objekt-orienteret programmering Slide Note Contents Index References | Genbrug af klasser er et af de store håb ved objekt-orienteret programmering. Genbrug af klasser er bedre end blot genbrug af procedurer. Men det ville være endnu bedre at kunne genbruge større enheder, såsom grupper af samarbejdende klasser eller hele programmer. Dette er den grundliggende problemstilling i denne lektion |
|
|
Introduktion gennem et eksempel |
Iterator ideen Slide Note Contents Index References | Vi starter konkret ved at se på et designmønster, som vi tidligere har benyttet til gennemløb af lister. Da vi oprindelig så på Iterator omtalte vi det ikke som et designmønster. Det var blot en god løsning på et ikke trivielt problem, som man ofte møder. Men dette viser sig at være den essentielle definition på et designmønster |
|
|
|
|
Iterator opfattet som et designmønster Slide Note Contents Index References | Her vil vi beskrive Iterator opfattet som et designmønster. Vi følger beskrivelses skabelonen fra Gamma og co. 'Design pattern bog', jf. henvisningen herunder. For nogle forhold beskriver vi faktiske egenskaber af Iterator. For andre forhold (kursiverede) beskriver på et mere overordnet niveau hvad de enkelte dele af dokumentationsskabelonen indeholder |
|
Betegnelsen 'aggregate' klassen hentyder naturligvis til en klasse som aggregerer en række bestanddele. Vi kalder også undertiden en sådan klasse for en Container. Polymorf iteration opnås ved at definere én fælles grænseflade (ala Java interface) for en bred klasse af gennemløb. Derved overlever en klientklasse selv om den underliggende liste og/eller iterator skiftes ud |
Figure. Strukturdiagram for en iterator, hvor vi anvender generelle begreber i modsætning til listebegreber. Her og i det følgende benytter vi en OMT agtig grafisk notation, ligesom i Design Patterns bogen. Notationen ligner UML, som den kendes fra kurset System Analyse og Design. Vi forklarer notationen i takt med at vi skrider frem. Den stiplede pil fra ConcreteAggregate til ConcreteIterator betyder at ConcreteAggregate har skabt ConcreteIterator | ![]() |
|
|
Design Patterns bogen, som er refereret ovenfor, er den mest kendt og udbredte bog om mønstre. Men der er også en del andre. En del af materialet i disse noter er tilpasset direkte fra denne bog. |
Generelt om designmønstre |
Hvem er Christopher Alexander? Slide Note Contents Index References | Designmønster interessen kan på en meget direkte måde spores tilbage til arkitektur faget, hvor en enkelt person's ideer har haft afgørende indflydelse på design af ting i vores fysiske verden |
|
|
|
|
|
Hvad er et designmønster? Slide Note Contents Index References | Vi byder her på en konkret definition af designmønster begrebet. Definitionen stammer fra Brad Appletons WWW artikel: 'Patterns and Software: Essential Concepts and Terminology', som er refereret på denne side |
The concept designmønster: Et designmønster er et navngiven 'guldkorn' af instruktiv information, som indfanger væsentlige strukturer og indsigt om en succesfuld familie af velafprøvede løsninger på tilbagevendende problemer i en bestemt kontekst, hvor forskellige kræfter kan trække i hver deres retning | Et designmønster er en nyttig instruktion, som fortæller hvordan man løser et bestemt problem. Problemet er karakteriseret af, at man møder det igen og igen i bestemte sammenhænge (kontekster). I disse sammenhænge skal der tages mangeartede hensyn, idet der er 'kræfter', som ofte virker i forskellige retninger. Løsningen på problemet er karakteriseret af at den er velafprøvet. Man kender altså konsekvenserne af at anvende den beskrevne løsning på problemet. |
|
|
Egenskaber ved et godt designmønster Slide Note Contents Index References | Vi lister her de fire væsentligste egenskaber ved designmønstre. Man kan også tænke på disse som kriterier for, at noget er et design pattern. |
|
Designmønstre på forskellige niveauer Slide Note Contents Index References | Designmønstre kan optræde på vidt forskellige abstraktionsniveauer. Man kan videre observere, at mønstre kan optræde andre steder end i design og programmering. Man taler således om analysemønstre |
|
|
|
Katalogisering af design patterns Slide Note Contents Index References | Det væsentligste bidrag af Design Pattern bogen, som vi har omtalt ovenfor (og tilsvarende bøger) er at den katalogiserer designmønstre til gavn og glæde for softwareudviklere og studerende. Design Pattern bogen er således et katalog bestående af 23 forskellige mønstre, der beskrives ud fra en standard disposition |
|
|
Udvalgte designmønstre |
Singleton (1) Slide Note Contents Index References | Designmønstret, der hedder Singleton, garanterer at der kun kan laves én instans af en klasse |
|
I den naive løsning taler vi om en global variabel, som indeholder den ene instans vi vi er interesseret i. Vi kan observere, at sådanne globale variable ikke findes i Java. Det nærmeste vi kommer er statiske (klasse) variable. Sådanne kan helt klart anvendes til formålet. Singleton er dog en anelse smartere end blot en statisk, globalt tilgængelig variabel, som refererer til 'det enestående objekt' |
Figure. Et klassediagram for Singleton designmønstret. Det er skitseret i boksen til højre hvordan instance metoden laver en memoriseret instans. (Bemærk at vi ikke følger præcis Java syntaks i dette eller de følgende diagrammer). | ![]() |
Singleton (2) Slide Note Contents Index References | Vi fortsætter her behandlingen af designmønstret Singleton. Vi koncentrerer os om en Java implementation af singleton |
|
Program: Skabelonen for en Singleton klasse i Java.
Ifølge mønstret fra Design Patterns bogen bør konstruktoren i Singleton klassen være protected. Dermed
kan den anvendes fra evt. subklasser. Men på grund af Java's specielle regler om protectede egenskaber i en klasse
(hvor disse også er pakkesynlige) skal konstruktoren være privat for at sikre, at ingen klient får lejlighed til
at instantiere en singleton klasse |
|
Program: Anvendelse af en Singleton klasse fra en anden klasse i Java.
Vi tester om s2 og s3 virkelig refererer til det samme objekt, den eneste instans af Singleton, som vi tillader at instantiere |
|
|
Factory Method (1) Slide Note Contents Index References | Dette designmønster retter sig mod central kontrol af instantiering af klasser. I stedet for at at have mange konkrete instantieringer af 'en produktklasse' rundt omkring i et program overlades ansvaret for produkt instantiering til et fabrikationsobjekt, som foretager instantieringen for os. Dette fabrikationsmellemled skaber en betydelig fleksibilitet. F.eks. kan vi med ét slag sørge for, at en anden klasse end den oprindeligt tiltænkte, bliver instantieret |
|
|
Figure. Klassediagram for Factory Method designmønstret.
Vi ser en abstrakt Creator (factory, altså fabrikations) klasse hvor operationen
anOperation skaber et produktobjekt gennem den abstrakte metode factoryMethod. Tænk på Creator klassen som en eller anden 'applikations klasse', der har behov for at skabe en produktklasse. Creatorklassen, som er abstrakt og dermed på et højt abstraktionsniveau, har ikke viden om, hvilket produktobjekt der skal skabes. Det overlades til en subklasse af Creator klassen at tage denne beslutning. Forskellige subklasser af Creator klassen kan instantiere forskellige produkter, som måske alle har et bestemt Interface tilfælles. Man kan også forestilige sig, at instantieringen kan variere hen over tid på anden vis. Bemærk at klasser og metoder, som er sat med kursiv skrift, er abstrakte. Den stiplede pil fra ConcreteCreator til ConcreteProduct fortæller, at creatoren skaber et produktobjekt | ![]() |
Factory Method (2) Slide Note Contents Index References | Vi ser her på konsekvenserne af at bruge mønstret Factory Method |
|
Composite (1) Slide Note Contents Index References |
|
Figure. Et velkendt eksempel på brugen af Composite design mønstret på grafiske objekter, hvoraf nogle (containere) kan indeholde andre grafiske objekter. Klassen Picture er en sådan container, som altså kan indholde andre Picture objekter, og ultimativt et antal Line, Rectangle og Text objekter. På figuren symboliserer diamanten som udgår fra Picture aggregering. Den sorte cirkel i enden af relationen (op imod den abstrakte klasse Graphics) symboliserer flere mulige grafik bestanddele. | ![]() |
I Component klassehierarkiet svarer Container til klassen Picture ovenfor. Klassen Graphic svarer i AWT til Component. Metoden Draw i diagrammet modsvares af en række forskellige metoder i Component, såsom paint, repaint og print |
|
Composite (2) Slide Note Contents Index References | Her fortsættes behandlingen af Composite designmønstret |
Figure. Det generelle strukturdiagram for Composite designmønstret. Det er værd at bemærke, at strukturen som er vist generelt definerer et træ, hvor Composite objekter er de indre knuder, og hvor Leaf objekter (som navnet antyder) er blade | ![]() |
Konsekvenser: |
Man kan spørge sig selv om, hvorfor det er et problem at understøtte metoder add, delete og getchild på Leaf objekter. Disse operationer nedarves naturligivs fra Component klassen. Problemet er imidlertid, at de ikke giver mening på Leaf objekter; Et Leaf objekt er et terminalt Component objekt, og er som sådan karakteriseret af at det ikke kan have 'børn'. |
Command (1) Slide Note Contents Index References | Vi ser her på Command mønstret, som materialserer en handling i et objekt |
|
Figure. Et klassediagram for Command design mønstret.
Læg mærke til grænsefladen af Command: execute, undo og redo. Execute virker
her på et objekt, som er 'receiver' af kommandoens handlinger. Altså sker kommandoens påvirkning
i dette mønster på receiver objektet. Vi ser endvidere at en kommando er en del af en Invoker. Invoker kan f.eks. være et menuitem, som indeholder (eller refererer til) et kommandoobjekt, som udføres når menuen aktiveres | ![]() |
Command (2) Slide Note Contents Index References |
|
Command (3) Slide Note Contents Index References | Vi slutter Command mønstret af med at sammenligne det med Java AWT's event listeners |
|
|
|
Decorator (1) Slide Note Contents Index References | Decorator er et designmønster hvormed vi dynamisk kan 'udvide et objekt' med nye egenskaber. |
Statisk set kan vi tilføje nye egenskaber ved at lave en udvidelse (ved brug af nedarvning mellem klasser). Men dette virker ikke dynamisk, på eksisterende objekter. Ideen bag Decorator er at understøtte udvidelse af eksisterende objekter, på 'run time'. |
Figure. Et prototypisk eksempel på Decorator designmønstret, hvor en tekst dekoreres med ramme og scrollbar. Kæden af objekter, hvor de forreste to dekorerer text view objektet. Når der opereres på aBorderDecorator tegnes kanten, hvorefter aScrollDecorator tilsendes samme besked. Når der opereres på aScrollDecorator tegnes scroll bars, hvorefter aTextView sendes samme besked. Når der opereres på aTextView tegnes det tekstuelle syn på objekter (det egentlige arbejde) | ![]() |
|
Decorator (2) Slide Note Contents Index References | Nu hvor vi har set på den dynamiske objekt-struktur af Decorator vil vi se på (et eksempel på) den statiske klasse-struktur |
Figure. Et eksempel på anvendelse af Decorator designmønstret.
I eksemplet dekoreres tekst view med scrollbars og border. Når en decorator tegner sig selv
tegnes også det dekorerede objekt, som igen kan være en dekorator, eller ultimativ et tekst view
objekt i dette eksempel. En klient, som arbejder på et TextView objekt, skal arbejde med en reference af typen VisualComponent. Da både et TextView og de forskellige decorator objekter er VisualComponents hænger det hele samme rent typemæssig, via typersammenlignelighed reglerne i Java. | ![]() |
Konsekvenser: |
Objekterne i kæden kan siges at delegere arbejde til hinanden. Vi har tidligere i materialet mødt delegering, og vi har konsteret at delegering undertiden er et alternativ til nedarvning. I denne sammenhæng bliver det understreget, at delegering er et mere dynamisk alternativ en nedarvning. Når en klasse B arver fra A, er instanser af B hele objekter uden nogen form for delobjekter. Når et B-objekt derimod delegerer arbejdet til et A-objekt er der to objekter i spil. På denne måde er identiteten af fænomenet delt ud på to separate objekter, hvilket kan give komplikationer i visse sammenhænge. |
Decorator (3) Slide Note Contents Index References | Vi afslutter diskussionen af Decorators med et eksempel, vi tidligere er stødt på, nemlig filter streams i Java bibliotekerne |
|
|
|
Observer (1) Slide Note Contents Index References | Observer er sikkert den mest kendte (og én af de mest nyttige) designmønstre overhovedet. Vi har studeret den ganske nøje i en tidligere lektion, men vi gentager den her, for at understreget de egenskaber ved Observer, som man skal holde fast i når 'vi har designmønster briller på' |
|
Figure. Et klassediagram for Observer designmønstret. Subject klassen svarer til Observable, jf. brugergrænseflade lektionen. Når et subjekt objekt er opdateret broadcastes dette til observer objekterne via den abonnementsordning, der er mellem subjekt og observatører. I ConcreteObserver ser vi at update metoden umiddelbar sender beskeden getState i subject objektet, for at finde ud af, hvad der egentlig skete. På næste side ser vi et objekt diagram, hvor disse interaktioner bliver illustreret på en meget klar måde | ![]() |
|
Observer (2) Slide Note Contents Index References | Observer er et mønster med en relativ kompliceret adfærd. Design Patterns bogen klassificerer den som et 'behavioral pattern' (som kontrast til 'structural' og 'creational patterns'). Derfor er det givende at vise en dynamisk model af mønstret, hvor vi ser hvordan subjekt og observer objekter interagerer indbyrdes |
Figure. En konkret observatør ændres, hvorved tilstanden i modellen (subject objektet) ændres.
Du skal forestille dig, at vi er igang med at operere på en brugergrænseflade, f.eks. at vi trykker på en knap.
Derved ændres modellen. Den øverste setState besked er en følge af denne tilstandsændring. (Det er ikke sikkert,
at beskeden kommer fra en observatør; Hvis vi følger model-view-controller tankegangen, vil den nok komme fra
en listener). Subject objektet sender sig selv Notify beskeden, som meddeler alle observatører, at modellen er opdateret. Hver observatør henter den fornødne tilstand (eller tilstandsændring) i subject objektet med henblik på at opdatere sig selv | ![]() |
Observer (3) Slide Note Contents Index References | Ligesom vi har gjort for de andre patterns, vil vi også for observers liste de vigtige konsekvenser, som anvendelse af mønstret kan forårsage |
Konsekvenser: |
|
Object Adapter (1) Slide Note Contents Index References | Adapter er et nyttigt pattern, som tilpasser grænsefladen af en klasse C til bestemte krav eller forventninger, til en klient. I stedet for at lave klassen om kan vi indskyde et mellemled, som tilpasser C. Vi ser her på en variant af Adapter, som kaldes Object Adapter |
|
Figure. Et klassediagram for Adapter, hvor et Adapter objekt tilpasses via en associering til en Adaptee objekt | ![]() |
I forbindelse med, at en Adapter kan tilpasse et helt Adaptee klassehierarki forestiller vi os altså, at Adaptee klassen har en række subklasser. Den ene Adapter, som er organiseret som ovenfor, tilpasser dem alle |
Klasse Adapter (2) Slide Note Contents Index References | I stedet for at involvere to objekter af typerne hhv. Adapter og Adaptee kan vi organisere tilpasningen gennem nedarvning. Dette giver en 'Class Adaptor' i kontrast til den ovenfor beskrevne 'Object Adapter' |
Figure. En Class Adapter med multipel nedarvning. I Java skal mindst én af Target og Adaptee være et Interface. Det er klart, at det er Target der er kandidat til at være et Interface, idet der jo er betydelig substans vi ønsker at bibringer Adapter fra Adaptee. Typemæssigt ønsker vi dog, at Adapter er en Target. Altså er Adapter en specialisering af Target | ![]() |
|
Adapter (3) Slide Note Contents Index References | Vi ser her på konsekvenserne ved at bruge et Adapter designmønster |
|
|
|
Chapter 15: Designmønstre
Course home Author home About producing this web Previous lecture (top) Next lecture (top) Previous lecture (bund) Next lecture (bund)
Generated: March 31, 2008, 12:10:00