



<html>
<head>
  <title>javabog.dk -  - Model-View-Controller-arkitekturen</title>
  <link rev="stylesheet" type="text/css" href="../typografi.css">
  <meta name="description" content="Lærebog i Java. Af Jacob Nordfalk. Udkommet hos Forlaget Globe">
  <meta name="keywords" content="designmønster, programmering, OOP, objekter, klasser, objektorienteret programmering, Java, JSP, lærebog, UML, IT">
</head>
<body bgcolor="#ffffff">



<a href='http://javabog.dk/'>javabog.dk</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a href='kapitel18.jsp'>&lt;&lt; forrige</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a href='indhold.jsp'>indhold</a>&nbsp;&nbsp;|&nbsp;&nbsp;n&aelig;ste &gt;&gt;&nbsp;&nbsp;|&nbsp;&nbsp;<a href='kode/'>programeksempler</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a href='../index_VP.html'>om bogen</a>

<H1 CLASS="western" STYLE="">19 <a name='afsn19'></a>Model-View-Controller-arkitekturen</H1>
<DIV ID="Indholdsfortegnelse5">
  <P STYLE="margin-top: 0.3cm; margin-bottom: 0cm"><BR>
  </P>
  <P STYLE="margin-left: 0.3cm; margin-top: 0.15cm; margin-bottom: 0cm">
  <FONT FACE="Helvetica, sans-serif"><FONT SIZE=2 STYLE="font-size: 11pt"><B>19.1
  De tre dele af MVC  320</B></FONT></FONT></P>
  <P STYLE="margin-left: 0.6cm; margin-top: 0.08cm; margin-bottom: 0cm">
  <FONT FACE="Helvetica, sans-serif"><FONT SIZE=2 STYLE="font-size: 9pt">19.1.1
  Modellen  320</FONT></FONT></P>
  <P STYLE="margin-left: 0.6cm; margin-top: 0.08cm; margin-bottom: 0cm">
  <FONT FACE="Helvetica, sans-serif"><FONT SIZE=2 STYLE="font-size: 9pt">19.1.2
  Pr&aelig;sentationen  320</FONT></FONT></P>
  <P STYLE="margin-left: 0.6cm; margin-top: 0.08cm; margin-bottom: 0cm">
  <FONT FACE="Helvetica, sans-serif"><FONT SIZE=2 STYLE="font-size: 9pt">19.1.3
  Kontrol-delen  321</FONT></FONT></P>
  <P STYLE="margin-left: 0.3cm; margin-top: 0.15cm; margin-bottom: 0cm">
  <FONT FACE="Helvetica, sans-serif"><FONT SIZE=2 STYLE="font-size: 11pt"><B>19.2
  Relationer mellem delene  321</B></FONT></FONT></P>
  <P STYLE="margin-left: 0.6cm; margin-top: 0.08cm; margin-bottom: 0cm">
  <FONT FACE="Helvetica, sans-serif"><FONT SIZE=2 STYLE="font-size: 9pt">19.2.1
  Informationsstr&oslash;m gennem MVC  321</FONT></FONT></P>
  <P STYLE="margin-left: 0.6cm; margin-top: 0.08cm; margin-bottom: 0cm">
  <FONT FACE="Helvetica, sans-serif"><FONT SIZE=2 STYLE="font-size: 9pt">19.2.2
  Opdatering af pr&aelig;sentationen - tre muligheder  322</FONT></FONT></P>
  <P STYLE="margin-left: 0.6cm; margin-top: 0.08cm; margin-bottom: 0cm">
  <FONT FACE="Helvetica, sans-serif"><FONT SIZE=2 STYLE="font-size: 9pt">19.2.3
   A - Pr&aelig;sentationer unders&oslash;ger modellen  322</FONT></FONT></P>
  <P STYLE="margin-left: 0.6cm; margin-top: 0.08cm; margin-bottom: 0cm">
  <FONT FACE="Helvetica, sans-serif"><FONT SIZE=2 STYLE="font-size: 9pt">19.2.4
   B - Kontroldel underretter pr&aelig;sentationer  322</FONT></FONT></P>
  <P STYLE="margin-left: 0.6cm; margin-top: 0.08cm; margin-bottom: 0cm">
  <FONT FACE="Helvetica, sans-serif"><FONT SIZE=2 STYLE="font-size: 9pt">19.2.5
   C - Modellen underretter pr&aelig;sentationer  323</FONT></FONT></P>
  <P STYLE="margin-left: 0.3cm; margin-top: 0.15cm; margin-bottom: 0cm">
  <FONT FACE="Helvetica, sans-serif"><FONT SIZE=2 STYLE="font-size: 11pt"><B>19.3
  Eksempel - bankkonti  323</B></FONT></FONT></P>
  <P STYLE="margin-left: 0.6cm; margin-top: 0.08cm; margin-bottom: 0cm">
  <FONT FACE="Helvetica, sans-serif"><FONT SIZE=2 STYLE="font-size: 9pt">19.3.1
  Modellen  324</FONT></FONT></P>
  <P STYLE="margin-left: 0.6cm; margin-top: 0.08cm; margin-bottom: 0cm">
  <FONT FACE="Helvetica, sans-serif"><FONT SIZE=2 STYLE="font-size: 9pt">19.3.2
  Pr&aelig;sentationer  325</FONT></FONT></P>
  <P STYLE="margin-left: 0.6cm; margin-top: 0.08cm; margin-bottom: 0cm">
  <FONT FACE="Helvetica, sans-serif"><FONT SIZE=2 STYLE="font-size: 9pt">19.3.3
  Kontroldel  326</FONT></FONT></P>
  <P STYLE="margin-left: 0.3cm; margin-top: 0.15cm; margin-bottom: 0cm">
  <FONT FACE="Helvetica, sans-serif"><FONT SIZE=2 STYLE="font-size: 11pt"><B>19.4
  Model-View - &quot;den lille MVC&quot;  328</B></FONT></FONT></P>
  <P STYLE="margin-left: 0.6cm; margin-top: 0.08cm; margin-bottom: 0cm">
  <FONT FACE="Helvetica, sans-serif"><FONT SIZE=2 STYLE="font-size: 9pt">19.4.1
  Adskillelse af pr&aelig;sentation og programlogik  328</FONT></FONT></P>
  <P STYLE="margin-left: 0.3cm; margin-top: 0.15cm; margin-bottom: 0cm">
  <FONT FACE="Helvetica, sans-serif"><FONT SIZE=2 STYLE="font-size: 11pt"><B>19.5
  Opgaver  328</B></FONT></FONT></P>
  <P STYLE="margin-left: 0.6cm; margin-top: 0.08cm; margin-bottom: 0cm">
  <FONT FACE="Helvetica, sans-serif"><FONT SIZE=2 STYLE="font-size: 9pt">19.5.1
  L&oslash;sning  329</FONT></FONT></P>
  <P STYLE="margin-top: 0.3cm; margin-bottom: 0cm"><BR>
  </P>
</DIV>











<P CLASS="kapiteloversigt-western">Det er en god id&eacute; at kigge
i dette kapitel f&oslash;r man l&aelig;ser <a href='kapitel6.jsp'>kapitel 6</a>, Grafiske brugergr&aelig;nseflader (Swing).</P>
<P CLASS="western" STYLE="">De fleste
programmer med en brugergr&aelig;nseflade kan inddeles i tre dele,
nemlig:</P>
<OL>
  <LI><P CLASS="western"><I>datamodellen</I>, som repr&aelig;senterer
  data og de bagvedliggende beregninger</P>
  <LI><P CLASS="western"><I>pr&aelig;sentationen</I> af data over for
  brugeren</P>
  <LI><P CLASS="western">brugerens mulighed for at <I>&aelig;ndre</I>
  i disse data gennem forskellige handlinger.</P>
</OL>
<P CLASS="western">Ofte pr&aelig;senteres brugeren ikke for alle
data, m&aring;ske kan han ikke &aelig;ndre dem frit, og m&aring;ske
er der konsekvenser for andre data i det samme eller i andre
sk&aelig;rmbilleder.</P>
<H2 CLASS="western">19.1 <a name='afsn19.1'></a>De tre dele af MVC</SPAN></H2>
<P CLASS="western">Model-View-Controller-arkitekturen (forkortet MVC)
er et designm&oslash;nster beregnet til programmer med en
brugergr&aelig;nseflade. 
</P>
<P CLASS="western">Den anbefaler at man opdeler programmet (i hvert
fald mentalt) i tre dele: En model, en pr&aelig;sentation, og en
kontrol-del:</P>
<P CLASS="western" ALIGN=CENTER><IMG SRC="bog20_html_50f606b1.gif" NAME="Object4"></P>
<P ALIGN=CENTER STYLE="margin-top: 0.11cm; margin-bottom: 0.11cm"><FONT SIZE=2 STYLE="font-size: 9pt"><I>Model-View-Controller-arkitekturen
i dens grundform. Pilene viser, hvilke <BR>dele der kender til
hverandre (der kan v&aelig;re flere - se <a href='kapitel19.jsp#afsn19.2.2'>afsnit 19.2.2</a> og frem).</I></FONT></P>
<H3 CLASS="western">19.1.1 <a name='afsn19.1.1'></a>Modellen</H3>
<P CLASS="western">Datamodellen <I>indeholder data og registrerer,
hvilken tilstand den p&aring;g&aelig;ldende del af programmet er i</I>.
Oftest er data indkapslet s&aring;dan, at konsistens sikres. I s&aring;
fald er der kun adgang til at sp&oslash;rge og &aelig;ndre p&aring;
data gennem metodekald.</P>
<P CLASS="western">Modellen b&oslash;r v&aelig;re uafh&aelig;ngig af,
hvordan data pr&aelig;senteres over for brugeren, og er der flere
programmer, der arbejder med de samme slags data, kan de i princippet
have den samme datamodel, selvom de i &oslash;vrigt er helt
forskellige.</P>
<P CLASS="western"><SPAN LANG="da-DK">Eksempel: En bankkonto har navn
p&aring; ejer, kontonummer, kort-ID, saldo, bev&aelig;gelser,
renteoplysninger etc. Saldoen kan ikke &aelig;ndres direkte, men med
handlingerne overf&oslash;rsel, udbetaling og indbetaling kan saldoen
p&aring;virkes (se eksempelvis klassen Kontomodel i <a href='kapitel19.jsp#afsn19.3.1'>afsnit 19.3.1</a>).</SPAN></P>
<P CLASS="western">Bem&aelig;rk, hvordan modellen for en bankkonto er
universel. Modellen kunne f.eks. anvendes b&aring;de i et program til
en pengeautomat, i et netbank-system og i programmet, som
ekspeditionsmedarbejderen anvender ved skranken.</P>
<H3 CLASS="western">19.1.2 <a name='afsn19.1.2'></a>Pr&aelig;sentationen</H3>
<P CLASS="western">Pr&aelig;sentationen (eng.: View) <I>henter
relevante data fra modellen og viser dem for brugeren i en passende
form</I>. Selvom to pr&aelig;sentationer deler model (viser data fra
samme model), kan de v&aelig;re meget forskellige, da de er beregnet
p&aring; en bestemt brugergr&aelig;nseflade.</P>
<P CLASS="western">Eksempel: Bankkontoen pr&aelig;senteres meget
forskelligt. I en pengeautomat vises ingen personlige oplysninger
overhovedet. I et netbank-system kan saldo og bev&aelig;gelser ses
(det kunne v&aelig;re en webl&oslash;sning i HTML, f.eks. en servlet
eller JSP-side). Ved skranken kan medarbejderen se endnu mere, f.eks.
filial og kontaktperson i banken (det kunne v&aelig;re implementeret
som en grafisk applikation, der k&oslash;rer hos brugeren).</P>
<H3 CLASS="western">19.1.3 <a name='afsn19.1.3'></a>Kontrol-delen</H3>
<P CLASS="western">Kontroldelen (eng.: controller) definerer, hvad
programmet kan. Den <I>oms&aelig;tter brugerens indtastninger,
museklik mv. til handlinger, der skal udf&oslash;res p&aring;
modellen</I>. 
</P>
<P CLASS="western">Eksempel: I pengeautomat kan man kun h&aelig;ve
penge. I et netbank-system kan brugeren m&aring;ske lave visse former
for overf&oslash;rsel fra sin egen konto. Ved skranken kan
medarbejderen derudover foretage ind- og udbetalinger.</P>
<H2 CLASS="western">19.2 <a name='afsn19.2'></a>Relationer mellem delene</SPAN></H2>
<P CLASS="western">Forestil dig, at modellen, pr&aelig;sentationen og
kontroldelen udg&oslash;res af hver sin klasse. Hvad er s&aring;
relationerne mellem klasserne?</P>
<P CLASS="western">Det er klart, at pr&aelig;sentationen og
kontroldelen, for at kunne fremvise hhv. manipulere med modellen,
skal kende til modellen og dens metoder. Hvilke andre bindinger er
der?</P>
<H3 CLASS="western">19.2.1 <a name='afsn19.2.1'></a>Informationsstr&oslash;m gennem MVC</H3>
<P CLASS="western">Figuren herunder illustrerer, hvordan str&oslash;mmen
af information g&aring;r fra modellen via pr&aelig;sentationen til
brugeren (symboliseret ved et &oslash;je). Brugeren foretager nogle
handlinger (symboliseret ved musen), som via kontrol-delen fortolkes
som nogle &aelig;ndringer, der foretages p&aring; modellen, hvorefter
de nye data vises for brugeren.</P>
<P CLASS="western" ALIGN=CENTER><IMG SRC="bog20_html_m354bb29e.gif" NAME="Object3" ALIGN=MIDDLE></P>

<H3 CLASS="western" STYLE="">19.2.2 <a name='afsn19.2.2'></a>Opdatering
af pr&aelig;sentationen - tre muligheder</H3>
<P CLASS="western">Pr&aelig;sentationen er normalt<A CLASS="sdfootnoteanc" NAME="sdfootnote1anc" HREF="#sdfootnote1sym"><SUP>1</SUP></A>
n&oslash;dt til p&aring; en eller anden m&aring;de at f&aring; at
vide, n&aring;r der er sket en &aelig;ndring i modellens data, s&aring;
den kan opdatere sk&aelig;rmbilledet.</P>
<P CLASS="western">Det kunne ske p&aring; tre m&aring;der: Enten m&aring;
pr&aelig;sentationen regelm&aelig;ssigt unders&oslash;ge modellen for
at opdage &aelig;ndringer, eller ogs&aring; m&aring; kontroldelen
eller modellen fort&aelig;lle pr&aelig;sentationen, at noget er
&aelig;ndret. Lad os se p&aring; alle tre muligheder.</P>
<H3 CLASS="western">19.2.3 <a name='afsn19.2.3'></a> A - Pr&aelig;sentationer unders&oslash;ger
modellen</H3>
<P CLASS="western">Hvis pr&aelig;sentationen regelm&aelig;ssigt skal
unders&oslash;ge model-klassen for at opdage &aelig;ndringerne, vil
&aelig;ndringerne naturligvis dukke op p&aring; brugerens sk&aelig;rm
med en vis forsinkelse, der kan virke forvirrende for brugeren.
Forsinkelsen kan naturligvis mindskes ved at unders&oslash;ge
modellen meget ofte (dette kaldes polling), men det er en ineffektiv
l&oslash;sning, der kr&aelig;ver meget processortid.</P>
<P CLASS="western">Denne mulighed er dog velegnet i de tilf&aelig;lde,
hvor der skal ske opdateringer af sk&aelig;rmen s&aring; ofte som
overhovedet muligt (f.eks. i et computerspil, hvor animationerne skal
v&aelig;re s&aring; flydende som muligt). Her vil pr&aelig;sentationen
konstant unders&oslash;ge modellen (for at tegne sk&aelig;rmbilledet),
og &aelig;ndringer vil derfor blive synlige n&aelig;sten omg&aring;ende.</P>
<P CLASS="western">I andre tilf&aelig;lde ligger det i sagens natur,
at der <I>altid</I> sker en fremvisning lige efter en opdatering. Det
g&aelig;lder f.eks. webl&oslash;sninger som servletter/JSP beskrevet
i <a href='kapitel14.jsp#afsn14.2'>afsnit 14.2</a>, Webservere (servletter og JSP), hvor netl&aelig;seren
altid foretager en anmodning (der indeholder formulardata, som
kontroldelen oms&aelig;tter til kald til modellen) og f&aring;r
HTML-koden til et nyt sk&aelig;rmbillede tilbage som svar (genereret
af pr&aelig;sentationen ud fra modellen).</P>
<H3 CLASS="western">19.2.4 <a name='afsn19.2.4'></a> B - Kontroldel underretter pr&aelig;sentationer</H3>
<P CLASS="western">En oplagt mulighed er, at kontroldelen, efter hver
&aelig;ndring p&aring; modellen, underretter pr&aelig;sentationen om,
at noget er &aelig;ndret (og dermed opdaterer sk&aelig;rmen).</P>
<P CLASS="western">Det er en fin l&oslash;sning til mindre systemer,
men hvad nu hvis der er flere pr&aelig;sentationer (og kontroldele)
af den samme model? For at holde visningen over for brugeren korrekt
skal hver kontroldel holde styr p&aring; samtlige pr&aelig;sentationer.
Hver gang der kommer en ny pr&aelig;sentation til, skal kontroldelene
opdateres til at medtage den. I et lidt st&oslash;rre scenario kan
det kan give et ret uoverskueligt program.</P>

<H3 CLASS="western" STYLE="">19.2.5 <a name='afsn19.2.5'></a> C -
Modellen underretter pr&aelig;sentationer</H3>
<P CLASS="western">En anden, mere avanceret (men i l&aelig;ngden mere
enkel) l&oslash;sning er at l&aelig;gge opdateringsopgaven hos
modellen, s&aring;dan at den fort&aelig;ller det til pr&aelig;sentationen
(og andre interessenter), n&aring;r den &aelig;ndres:</P>
<P CLASS="western" ALIGN=CENTER><IMG SRC="bog20_html_m27c5ffdd.gif" NAME="Objekt1"></P>
<P ALIGN=CENTER STYLE="margin-top: 0.11cm; margin-bottom: 0.11cm"><FONT SIZE=2 STYLE="font-size: 9pt"><I>MVC
med pr&aelig;sentationer, som observerer modellen - den oftest brugte
variant af MVC.</I></FONT></P>
<P CLASS="western">Da modellen skal v&aelig;re uafh&aelig;ngig af
pr&aelig;sentationerne (f.eks. for at modellen kan genbruges med en
anden pr&aelig;sentation i et andet program), kan den n&oslash;dvendigvis
ikke vide noget om  pr&aelig;sentationerne direkte.</P>
<P CLASS="western">I stedet observerer pr&aelig;sentationerne
modellen p&aring; samme m&aring;de som i Javas h&aelig;ndelsessystem:
Pr&aelig;sentationerne registrerer sig som lyttere hos modellen, og 
modellen sender en h&aelig;ndelse til alle registrerede lytter n&aring;r
den &aelig;ndres. Den stiplede linje (underretning om &aelig;ndring)
illustrerer dette.</P>
<P CLASS="western">Dette er designm&oslash;nstret Observat&oslash;r
(beskrevet i <a href='kapitel17.jsp#afsn17.5'>afsnit 17.5</a>) og kan ogs&aring; udtrykkes i dette
designm&oslash;nsters ordvalg: Pr&aelig;sentationerne er
observat&oslash;rer, der observerer modellen. Modellen underretter
sine observat&oslash;rer, n&aring;r den &aelig;ndres. 
</P>
<H2 CLASS="western">19.3 <a name='afsn19.3'></a>Eksempel - bankkonti</SPAN></H2>
<P CLASS="western">Da den mere avancerede mulighed C stemmer overens
med Javas h&aelig;ndelsesmodel og er den mest generelle, er det den,
der oftest tages i anvendelse. Mulighed B er velegnet til simplere
systemer.</P>
<P CLASS="western">Her f&oslash;lger et t&aelig;nkt eksempel p&aring;
en bankkonto. Hver konto har en ejer, og man kan inds&aelig;tte, h&aelig;ve
og overf&oslash;re penge til en anden konto. 
</P>
<P CLASS="western">Mulighed C er valgt her, dvs. pr&aelig;sentationer
skal registrere sig hos modellen (ActionEvent er anvendt som
h&aelig;ndelse, s&aring; det vil sige, at lyttere skal implementere
ActionListener-interfacet).</P>
<P CLASS="western" ALIGN=CENTER><IMG SRC="bog20_html_m49cd8bde.gif" NAME="Objekt19"></P>
<H3 CLASS="western">19.3.1 <a name='afsn19.3.1'></a>Modellen</H3>
<P CLASS="western">Modellen har metoder, der svarer til
forretningslogikken i programmet, s&aring;som overf&oslash;r(), h&aelig;v()
og inds&aelig;t(). 
</P>
<P CLASS="western">Derudover har den metoderne addActionListener() og
removeActionListener() til at registrere lyttere (observat&oslash;rer)
p&aring; den.</P>
<PRE CLASS="kode-western">import java.util.*;
<SPAN LANG="da-DK">import java.awt.event.*;</SPAN>

<SPAN LANG="da-DK">public class Kontomodel</SPAN>
<SPAN LANG="da-DK">{</SPAN>
<SPAN LANG="da-DK">  private String ejer;</SPAN>
<SPAN LANG="da-DK">  private double saldo;</SPAN>
<SPAN LANG="da-DK">  private List bev&aelig;gelser = new ArrayList();         <I>// til historik</I></SPAN>

<SPAN LANG="da-DK">  public Kontomodel(String ejer1) { ejer = ejer1; }</SPAN>

<SPAN LANG="da-DK">  public double getSaldo() { return saldo; }</SPAN>
<SPAN LANG="da-DK">  public String getEjer() { return ejer; }</SPAN>

<SPAN LANG="da-DK">  public String toString() { return ejer + &quot;: &quot;+saldo+&quot; kr&quot;; }</SPAN>

<SPAN LANG="da-DK">  public void overf&oslash;r(Kontomodel til, double bel&oslash;b)</SPAN>
<SPAN LANG="da-DK">  {</SPAN>
<SPAN LANG="da-DK">    if (bel&oslash;b&lt;0) throw new IllegalArgumentException(</SPAN>
<SPAN LANG="da-DK">                                      &quot;Bel&oslash;b kan ikke v&aelig;re negativt eller nul.&quot;);</SPAN>

<SPAN LANG="da-DK">    saldo = saldo - bel&oslash;b;</SPAN>
<SPAN LANG="da-DK">    til.saldo = til.saldo + bel&oslash;b;<I>// privat variabel <B>kan</B> ses i samme <U>klasse</U></I></SPAN>

<SPAN LANG="da-DK">    String &aelig;ndring = &quot;Overf&oslash;rt &quot;+bel&oslash;b+&quot; fra &quot;+ejer+&quot; til &quot;+til.ejer;</SPAN>
<SPAN LANG="da-DK">    bev&aelig;gelser.add(&aelig;ndring);</SPAN>
<SPAN LANG="da-DK">    til.bev&aelig;gelser.add(&aelig;ndring);</SPAN>

<SPAN LANG="da-DK">    fort&aelig;lLyttere(&aelig;ndring);       <I>// besked til alle visninger af denne konto</I></SPAN>
<SPAN LANG="da-DK">    til.fort&aelig;lLyttere(&aelig;ndring);   <I>// besked til alle visninger af bel&oslash;bsmodtager</I></SPAN>
<SPAN LANG="da-DK">  }</SPAN>

<SPAN LANG="da-DK">  public void h&aelig;v(double bel&oslash;b)</SPAN>
<SPAN LANG="da-DK">  {</SPAN>
<SPAN LANG="da-DK">    if (bel&oslash;b&lt;0) throw new IllegalArgumentException(</SPAN>
<SPAN LANG="da-DK">                                      &quot;Bel&oslash;b kan ikke v&aelig;re negativt eller nul.&quot;);</SPAN>
<SPAN LANG="da-DK">    saldo = saldo - bel&oslash;b;</SPAN>

<SPAN LANG="da-DK">    String &aelig;ndring = &quot;H&aelig;vet &quot;+bel&oslash;b;</SPAN>
<SPAN LANG="da-DK">    bev&aelig;gelser.add(&aelig;ndring);</SPAN>
<SPAN LANG="da-DK">    fort&aelig;lLyttere(&aelig;ndring);       <I>// send besked til alle visninger</I></SPAN>
<SPAN LANG="da-DK">  }</SPAN>

<SPAN LANG="da-DK">  public void inds&aelig;t(double bel&oslash;b)</SPAN>
<SPAN LANG="da-DK">  {</SPAN>
<SPAN LANG="da-DK">    if (bel&oslash;b&lt;0) throw new IllegalArgumentException(</SPAN>
<SPAN LANG="da-DK">                                      &quot;Bel&oslash;b kan ikke v&aelig;re negativt eller nul.&quot;);</SPAN>
<SPAN LANG="da-DK">    saldo = saldo + bel&oslash;b;</SPAN>

<SPAN LANG="da-DK">    String &aelig;ndring = &quot;Indsat &quot;+bel&oslash;b;</SPAN>
<SPAN LANG="da-DK">    bev&aelig;gelser.add(&aelig;ndring);</SPAN>
<SPAN LANG="da-DK">    fort&aelig;lLyttere(&aelig;ndring);       <I>// Send besked til alle visninger</I></SPAN>
<SPAN LANG="da-DK">  }</SPAN>

<SPAN LANG="da-DK"><I>  //</I></SPAN>
<SPAN LANG="da-DK"><I>  // Underetning af h&aelig;ndelses-lyttere.</I></SPAN>
<SPAN LANG="da-DK"><I>  //</I></SPAN>

<SPAN LANG="da-DK">  <I>/** Lyttere til denne model */</I></SPAN>
<SPAN LANG="da-DK">   private List lyttere = new ArrayList(2);</SPAN>

<SPAN LANG="da-DK">  <I>/** Tilf&oslash;jer en lytter */</I></SPAN>
<SPAN LANG="da-DK">  public synchronized void addActionListener(ActionListener l)</SPAN>
<SPAN LANG="da-DK">  {</SPAN>
<SPAN LANG="da-DK">    lyttere.add(l);</SPAN>
<SPAN LANG="da-DK">  }</SPAN>

<SPAN LANG="da-DK">  <I>/** Fjerner en lytter */</I></SPAN>
<SPAN LANG="da-DK">  public synchronized void removeActionListener(ActionListener l)</SPAN>
<SPAN LANG="da-DK">  {</SPAN>
<SPAN LANG="da-DK">    lyttere.remove(l);</SPAN>
<SPAN LANG="da-DK">  }</SPAN>

<SPAN LANG="da-DK">  <I>/** Fort&aelig;ller alle lytter om en &aelig;ndring i modellen */</I></SPAN>
<SPAN LANG="da-DK">  private void fort&aelig;lLyttere(String &aelig;ndring)</SPAN>
<SPAN LANG="da-DK">  {</SPAN>
<SPAN LANG="da-DK"><I>    // opret h&aelig;ndelse, der beskriver &aelig;ndringen</I></SPAN>
<SPAN LANG="da-DK">    ActionEvent h&aelig;ndelse = new ActionEvent(this, 0, &aelig;ndring);</SPAN>
<SPAN LANG="da-DK">    for (Iterator i=lyttere.iterator(); i.hasNext(); )</SPAN>
<SPAN LANG="da-DK">    {</SPAN>
<SPAN LANG="da-DK">      ActionListener l = (ActionListener) i.next();</SPAN>
<SPAN LANG="da-DK">      l.actionPerformed(h&aelig;ndelse);<I>// underret l om h&aelig;ndelsen</I></SPAN>
<SPAN LANG="da-DK">    }</SPAN>
<SPAN LANG="da-DK">  }</SPAN>
<SPAN LANG="da-DK">}</SPAN></PRE><H3 CLASS="western">
19.3.2 <a name='afsn19.3.2'></a>Pr&aelig;sentationer</H3>
<P CLASS="western">Herunder to eksempler p&aring; pr&aelig;sentationer
af modellen. Den f&oslash;rste er ret enkel, da den 'viser' modellen
ved at udskrive p&aring; System.out, hver gang der sker en &aelig;ndring:</P>
<PRE CLASS="kode-western">import java.awt.event.*;
<SPAN LANG="da-DK">public class KontovisningTekst implements ActionListener</SPAN>
<SPAN LANG="da-DK">{</SPAN>
<SPAN LANG="da-DK">  private Kontomodel model;</SPAN>

<SPAN LANG="da-DK">  public KontovisningTekst(Kontomodel model1)</SPAN>
<SPAN LANG="da-DK">  {</SPAN>
<SPAN LANG="da-DK">    model = model1;</SPAN>
<SPAN LANG="da-DK">    model.addActionListener(this);        <I>// registr&eacute;r som lytter p&aring; modellen</I></SPAN>
<SPAN LANG="da-DK">  }</SPAN>

<SPAN LANG="da-DK">  public void actionPerformed(ActionEvent h&aelig;ndels)</SPAN>
<SPAN LANG="da-DK">  {</SPAN>
<SPAN LANG="da-DK"><I>    // getActionCommand() giver beskrivelsen af h&aelig;ndelsen</I></SPAN>
<SPAN LANG="da-DK">    System.out.println(&quot;Konto &quot;+model.getEjer()+&quot;: &quot;+h&aelig;ndels.getActionCommand());</SPAN>
<SPAN LANG="da-DK">    System.out.println(&quot;Konto &quot;+model.getEjer()+&quot;: Saldo er: &quot;+model.getSaldo());</SPAN>
<SPAN LANG="da-DK">  }</SPAN>
<SPAN LANG="da-DK">}</SPAN></PRE><P CLASS="western">
Uddata fra denne klasse kunne v&aelig;re:</P>
<PRE CLASS="western">Konto Jacob: Indsat 20.0<BR>Konto Jacob: Saldo er nu: 20.0<BR>Konto Jacob: Indsat 20.0<BR>Konto Jacob: Saldo er nu: 40.0<BR>Konto Jacob: Overf&oslash;rt 50.0 fra Jacob til Brian<BR>Konto Jacob: Saldo er nu: -10.0</PRE>
<P CLASS="western" STYLE="">Den anden er et
grafisk panel (i figuren til h&oslash;jre er det vist i et
applet-vindue). Ved hver &aelig;ndring i modellen bliver panelet
gentegnet.</P>
<PRE CLASS="kode-western">import javax.swing.*;
<SPAN LANG="da-DK">import java.awt.*;</SPAN>
<SPAN LANG="da-DK">import java.awt.event.*;</SPAN>
<SPAN LANG="da-DK">public class KontovisningPanel extends JPanel implements ActionListener</SPAN>
<SPAN LANG="da-DK">{</SPAN>
<SPAN LANG="da-DK">  p<IMG SRC="bog20_html_3bf16667.png" NAME="Grafik1" ALIGN=RIGHT BORDER=0>rivate Kontomodel model;</SPAN>
<SPAN LANG="da-DK">  private String meddelelse;</SPAN>

<SPAN LANG="da-DK">  public void paint(Graphics g)</SPAN>
<SPAN LANG="da-DK">  {</SPAN>
<SPAN LANG="da-DK">    super.paint(g);</SPAN>
<SPAN LANG="da-DK">    if (model == null) return;</SPAN>
<SPAN LANG="da-DK">    g.drawString(&quot;Konto &quot;+model.getEjer(),10,10);</SPAN>

<SPAN LANG="da-DK">    if (meddelelse != null)</SPAN>
<SPAN LANG="da-DK">    {</SPAN>
<SPAN LANG="da-DK">      g.drawString(meddelelse,10,25);</SPAN>
<SPAN LANG="da-DK"><I>      // n&aelig;ste gang der gentegnes skal meddelelsen ikke vises</I></SPAN>
<SPAN LANG="da-DK">      meddelelse = null;</SPAN>
<SPAN LANG="da-DK">    }</SPAN>

<SPAN LANG="da-DK">    if (model.getSaldo()&lt;0) g.setColor(Color.red); </SPAN>
<SPAN LANG="da-DK">    else g.setColor(Color.darkGray);</SPAN>
<SPAN LANG="da-DK">    g.drawString(&quot;saldo: &quot;+model.getSaldo(),10,40);</SPAN>
<SPAN LANG="da-DK">  }</SPAN>

<SPAN LANG="da-DK">  public void setModel(Kontomodel model1)</SPAN>
<SPAN LANG="da-DK">  {</SPAN>
<SPAN LANG="da-DK">    if (model != null) model.removeActionListener(this);</SPAN>
<SPAN LANG="da-DK">    model = model1;</SPAN>
<SPAN LANG="da-DK">    if (model != null) model.addActionListener(this);     <I>// lytter p&aring; modellen</I></SPAN>
<SPAN LANG="da-DK">  }</SPAN>

<SPAN LANG="da-DK">  public void actionPerformed(ActionEvent h&aelig;ndelse)</SPAN>
<SPAN LANG="da-DK">  {</SPAN>
<SPAN LANG="da-DK">    meddelelse = h&aelig;ndelse.getActionCommand();</SPAN>
<SPAN LANG="da-DK">    repaint();</SPAN>
<SPAN LANG="da-DK">  }</SPAN>
<SPAN LANG="da-DK">}</SPAN></PRE><H3 CLASS="western">
19.3.3 <a name='afsn19.3.3'></a>Kontroldel</H3>
<P CLASS="western">Herunder har vi lavet en applet, der opretter en
Jacob og en Brian-kontomodel. I appletten er der et antal
KontovisningPanel'er, der viser kontiene, og nogle knapper, der kan
&aelig;ndre p&aring; kontiene (disse knapper udg&oslash;r
kontrol-delen af programmet).</P>
<P CLASS="western" ALIGN=CENTER><IMG SRC="bog20_html_e36a4c8.png" NAME="Grafik9" ALIGN=BOTTOM BORDER=0></P>
<PRE CLASS="kode-western">import javax.swing.*;
<SPAN LANG="da-DK">import java.awt.*;</SPAN>
<SPAN LANG="da-DK">import java.awt.event.*;</SPAN>
<SPAN LANG="da-DK">public class KontokontrolApplet extends JApplet</SPAN>
<SPAN LANG="da-DK">{</SPAN>
<SPAN LANG="da-DK">  Kontomodel jacobsKonto = new Kontomodel(&quot;Jacob&quot;);</SPAN>
<SPAN LANG="da-DK">  Kontomodel briansKonto = new Kontomodel(&quot;Brian&quot;);</SPAN>

<SPAN LANG="da-DK">  KontovisningPanel panelJacob = new KontovisningPanel();</SPAN>
<SPAN LANG="da-DK">  KontovisningPanel panelBrian = new KontovisningPanel();</SPAN>
<SPAN LANG="da-DK">  KontovisningPanel panelJacobAndenVisning = new KontovisningPanel();</SPAN>

<SPAN LANG="da-DK">  JButton buttonJtilB50kr = new JButton();</SPAN>
<SPAN LANG="da-DK">  JButton buttonJ20krind = new JButton();</SPAN>
<SPAN LANG="da-DK">  JButton buttonB30krud = new JButton();</SPAN>

<SPAN LANG="da-DK">  public void init() {</SPAN>
<SPAN LANG="da-DK">    try {</SPAN>
<SPAN LANG="da-DK">      jbInit();</SPAN>
<SPAN LANG="da-DK">    }</SPAN>
<SPAN LANG="da-DK">    catch(Exception e) {</SPAN>
<SPAN LANG="da-DK">      e.printStackTrace();</SPAN>
<SPAN LANG="da-DK">    }</SPAN>
<SPAN LANG="da-DK">    panelJacob.setModel(jacobsKonto);       <I>// panelJacob lytter p&aring; jacobsKonto</I></SPAN>
<SPAN LANG="da-DK">    panelJacobAndenVisning.setModel(jacobsKonto); <I>// ditto</I></SPAN>
<SPAN LANG="da-DK">    panelBrian.setModel(briansKonto);       <I>// panelBrian lytter p&aring; briansKonto</I></SPAN>

<SPAN LANG="da-DK">    new KontovisningTekst(jacobsKonto);     <I>// tekstvisning p&aring; jacobsKonto</I></SPAN>
<SPAN LANG="da-DK">    new KontovisningTekst(briansKonto);     <I>// tekstvisning p&aring; briansKonto</I></SPAN>

<SPAN LANG="da-DK">    JFrame f = new JFrame(&quot;Brian&quot;);         <I>// lav ogs&aring; separat vindue til Brian</I></SPAN>
<SPAN LANG="da-DK">    KontovisningPanel panelBrianAndenVisning = new KontovisningPanel();</SPAN>
<SPAN LANG="da-DK">    panelBrianAndenVisning.setModel(briansKonto);</SPAN>

<SPAN LANG="da-DK">    f.getContentPane().add(panelBrianAndenVisning);</SPAN>
<SPAN LANG="da-DK">    f.setSize(150,100);</SPAN>
<SPAN LANG="da-DK">    f.validate();</SPAN>
<SPAN LANG="da-DK">    f.setVisible(true);</SPAN>
<SPAN LANG="da-DK">  }</SPAN>

<SPAN LANG="da-DK">  private void jbInit() throws Exception {</SPAN>
<SPAN LANG="da-DK">    this.getContentPane().setLayout(null);</SPAN>
<SPAN LANG="da-DK">    panelJacob.setBounds(new Rectangle(0, 0, 139, 102));</SPAN>
<SPAN LANG="da-DK">    panelBrian.setBounds(new Rectangle(250, 0, 150, 101));</SPAN>
<SPAN LANG="da-DK">    panelJacobAndenVisning.setBounds(new Rectangle(130, 105, 140, 84));</SPAN>
<SPAN LANG="da-DK">    buttonJtilB50kr.setText(&quot;50 kr -&gt;&quot;);</SPAN>
<SPAN LANG="da-DK">    buttonJtilB50kr.setBounds(new Rectangle(140, 33, 100, 25));</SPAN>

<SPAN LANG="da-DK"><I>    // n&aring;r der affyres en h&aelig;ndelse fra buttonJtilB50kr, s&aring; kald</I></SPAN>
<SPAN LANG="da-DK"><I>    // metoden buttonJtilB50kr_actionPerformed, defineret nedenfor</I></SPAN>
<SPAN LANG="da-DK">    buttonJtilB50kr.addActionListener(new java.awt.event.ActionListener() {</SPAN>
<SPAN LANG="da-DK">      public void actionPerformed(ActionEvent e) {</SPAN>
<SPAN LANG="da-DK">        buttonJtilB50kr_actionPerformed(e);</SPAN>
<SPAN LANG="da-DK">      }</SPAN>
<SPAN LANG="da-DK">    });</SPAN>

<SPAN LANG="da-DK">    buttonJ20krind.setText(&quot;Inds&aelig;t 20 kr&quot;);</SPAN>
<SPAN LANG="da-DK">    buttonJ20krind.setBounds(new Rectangle(6, 104, 124, 25));</SPAN>
<SPAN LANG="da-DK">    buttonJ20krind.addActionListener(new java.awt.event.ActionListener() {</SPAN>
<SPAN LANG="da-DK">      public void actionPerformed(ActionEvent e) {</SPAN>
<SPAN LANG="da-DK">        buttonJ20krind_actionPerformed(e);</SPAN>
<SPAN LANG="da-DK">      }</SPAN>
<SPAN LANG="da-DK">    });</SPAN>

<SPAN LANG="da-DK">    buttonB30krud.setText(&quot;H&aelig;v 30 kr&quot;);</SPAN>
<SPAN LANG="da-DK">    buttonB30krud.setBounds(new Rectangle(272, 105, 124, 25));</SPAN>
<SPAN LANG="da-DK">    buttonB30krud.addActionListener(new java.awt.event.ActionListener() {</SPAN>
<SPAN LANG="da-DK">      public void actionPerformed(ActionEvent e) {</SPAN>
<SPAN LANG="da-DK">        buttonB30krud_actionPerformed(e);</SPAN>
<SPAN LANG="da-DK">      }</SPAN>
<SPAN LANG="da-DK">    });</SPAN>
<SPAN LANG="da-DK">    this.getContentPane().add(panelBrian, null);</SPAN>
<SPAN LANG="da-DK">    this.getContentPane().add(panelJacob, null);</SPAN>
<SPAN LANG="da-DK">    this.getContentPane().add(buttonJtilB50kr, null);</SPAN>
<SPAN LANG="da-DK">    this.getContentPane().add(buttonJ20krind, null);</SPAN>
<SPAN LANG="da-DK">    this.getContentPane().add(buttonB30krud, null);</SPAN>
<SPAN LANG="da-DK">    this.getContentPane().add(panelJacobAndenVisning, null);</SPAN>
<SPAN LANG="da-DK">  }</SPAN>

<SPAN LANG="da-DK">  <I>// kontroldelen af programmet - &aelig;ndr i modellen efter brugerens handlinger</I></SPAN>
<SPAN LANG="da-DK">  void buttonJ20krind_actionPerformed(ActionEvent e) {</SPAN>
<SPAN LANG="da-DK">    jacobsKonto.inds&aelig;t(20);</SPAN>
<SPAN LANG="da-DK">  }</SPAN>

<SPAN LANG="da-DK">  void buttonB30krud_actionPerformed(ActionEvent e) {</SPAN>
<SPAN LANG="da-DK">    briansKonto.h&aelig;v(30);</SPAN>
<SPAN LANG="da-DK">  }</SPAN>

<SPAN LANG="da-DK">  void buttonJtilB50kr_actionPerformed(ActionEvent e) {</SPAN>
<SPAN LANG="da-DK">    jacobsKonto.overf&oslash;r(briansKonto,50);</SPAN>
<SPAN LANG="da-DK">  }</SPAN>
<SPAN LANG="da-DK">}</SPAN></PRE>

<H2 CLASS="western">19.4 <a name='afsn19.4'></a>Model-View - &quot;den lille MVC&quot;</SPAN></H2>
<P CLASS="western">Adskillelsen mellem pr&aelig;sentation og
kontroldel kan v&aelig;re besv&aelig;rlig eller uhensigtsm&aelig;ssig
at gennemf&oslash;re i praksis. 
</P>
<P CLASS="western">Det g&aelig;lder for eksempel, n&aring;r man
programmerer grafiske brugergr&aelig;nseflader i Java: Her er det
hensigtsm&aelig;ssigt at l&aelig;gge pr&aelig;sentation
(paint()-metode og grafiske komponenter) og kontrol-del
(h&aelig;ndelseslyttere p&aring; komponenterne) i samme klasse.</P>
<H3 CLASS="western">19.4.1 <a name='afsn19.4.1'></a>Adskillelse af pr&aelig;sentation og
programlogik</H3>
<P CLASS="western">Derfor begr&aelig;nses MVC undertiden til
&quot;Model-View&quot;-arkitekturen, hvor pr&aelig;sentation og
kontrol-del er sl&aring;et sammen.</P>
<P CLASS="western">Model-View-arkitekturen er id&eacute;en om at
adskille pr&aelig;sentation og programlogik, s&aring;dan at 
programlogikken (modellen) kan genbruges i andre sammenh&aelig;nge.</P>
<P CLASS="western">I denne begr&aelig;nsede udgave af MVC vil der
ofte kun v&aelig;re &eacute;n pr&aelig;sentation af data, og man kan
derfor se bort fra diskussionen i <a href='kapitel19.jsp#afsn19.2.2'>afsnit 19.2.2</a> om opdateringen af
pr&aelig;sentationen.</P>

<H2 CLASS="western">19.5 <a name='afsn19.5'></a>Opgaver</SPAN></H2>
<OL>
  <LI><P CLASS="western"><SPAN LANG="da-DK">Hent Konto-eksemplet fra
  </SPAN><SPAN STYLE="font-weight: medium"><A CLASS="western" HREF="http://javabog.dk/VP/kode/"><SPAN LANG="da-DK">http://javabog.dk/VP/kode/</SPAN></A></SPAN><SPAN LANG="da-DK">.
  Pr&oslash;v at k&oslash;re det. <BR>Hvordan kan det v&aelig;re, at
  visningerne af Kontomodel bliver opdateret?</SPAN></P>
  <LI><P CLASS="western">Opret KontovisningMedTekstArea, der er en
  klasse (JPanel eller JFrame), der viser (og opdaterer) en
  beskrivelse af en konto i et tekstfelt.<BR><IMG SRC="bog20_html_3b710ab2.png" NAME="KontovisningMedTekstArea" ALT="KontovisningMedTekstArea" ALIGN=BOTTOM BORDER=0></P>
</OL>


<H3 CLASS="western">19.5.1 <a name='afsn19.5.1'></a>L&oslash;sning</H3>

	    Dette afsnit er ikke omfattet af Åben Dokumentslicens.<br>
	    Du skal <a href="/index_VP.html#bestil">købe</a> bogen for at
	    måtte læse dette afsnit.

  <form action="http://javabog.dk/VP/kapitel19.jsp#afsn19.5.1">
  <input type='checkbox' name='vis' value='19.5.1'>Jeg erklærer, at jeg allerede har købt bogen<br />
  <input type='checkbox' name='vis' value='19.5.1'>Jeg lover at anskaffe den i nær fremtid.<br />
  <input type='submit' value='Vis mig dette afsnit'>
  </form>

	  <DIV ID="sdfootnote1">
  <P CLASS="sdfootnote-western"><A CLASS="sdfootnotesym" NAME="sdfootnote1sym" HREF="#sdfootnote1anc">1</A>I
  nogle tilf&aelig;lde bliver pr&aelig;sentationen <I>altid</I> kaldt
  efter en opdatering. Det g&aelig;lder f.eks. JSP-sider og
  servletter.</P>
</DIV>

<a href='http://javabog.dk/'>javabog.dk</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a href='kapitel18.jsp'>&lt;&lt; forrige</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a href='indhold.jsp'>indhold</a>&nbsp;&nbsp;|&nbsp;&nbsp;n&aelig;ste &gt;&gt;&nbsp;&nbsp;|&nbsp;&nbsp;<a href='kode/'>programeksempler</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a href='../index_VP.html'>om bogen</a>
<hr>
<font size=-2>http://javabog.dk/ - <b></b> af Jacob Nordfalk.
<br>
  Licens og kopiering under <a href='http://www.linuxbog.dk/licens.html'>&Aring;ben Dokumentlicens</a> (&Aring;DL)
  hvor intet andet er nævnt (71% af værket).
</font>
<br>
Ønsker du at se de sidste 29% af dette værk (362838 tegn)
skal du købe bogen. Så får du pæne figurer og layout, stikordsregister og en trykt bog med i købet.
<!-- netlæser: Wget/1.10, autoHent: true  -->
     

</body>
</html>
