Programmering

En indvendig visning af Observer

For ikke længe siden gik min kobling ud, så jeg fik min Jeep trukket til en lokal forhandler. Jeg kendte ingen i forhandleren, og ingen af ​​dem kendte mig, så jeg gav dem mit telefonnummer, så de kunne give mig et skøn. Dette arrangement fungerede så godt, at vi gjorde det samme, da arbejdet var færdigt. Fordi alt dette viste sig perfekt for mig, formoder jeg, at serviceafdelingen hos forhandleren bruger det samme mønster med de fleste af sine kunder.

Dette mønster for publicerings-abonnement, hvor en observatør registrerer sig med en emne og modtager efterfølgende underretninger, er ret almindelig, både i hverdagen og i den virtuelle verden af ​​softwareudvikling. Faktisk er det Observer mønster, som det er kendt, er en af ​​kernerne i objektorienteret softwareudvikling, fordi det lader forskellige objekter kommunikere. Denne mulighed lader dig tilslutte objekter til en ramme under kørsel, hvilket giver mulighed for meget fleksibel, udvidelig og genanvendelig software.

Bemærk: Du kan downloade denne artikels kildekode fra Resources.

Observer-mønsteret

I Designmønstre, beskriver forfatterne observatørmønsteret således:

Definer en til mange afhængighed mellem objekter, så når et objekt skifter tilstand, underrettes og opdateres alle dets afhængige automatisk.

Observer-mønsteret har et emne og potentielt mange observatører. Observatører registrerer sig med emnet, hvilket giver observatørerne besked, når der sker begivenheder. Det prototypiske observatøreksempel er en grafisk brugergrænseflade (GUI), der samtidigt viser to visninger af en enkelt model; visningerne registreres med modellen, og når modellen ændres, underretter den visningerne, som opdateres i overensstemmelse hermed. Lad os se, hvordan det fungerer.

Observatører i aktion

Applikationen vist i figur 1 indeholder en model og to visninger. Modelens værdi, som repræsenterer billedforstørrelse, manipuleres ved at flytte skyderknappen. Visningerne, kendt som komponenter i sving, er en etiket, der viser modelens værdi og en rulle rude, der skalerer et billede i overensstemmelse med modelens værdi.

Modellen i applikationen er en forekomst af DefaultBoundedRangeModel (), der sporer en afgrænset heltal - i dette tilfælde fra 0 til 100—Med disse metoder:

  • int getMaximum ()
  • int getMinimum ()
  • int getValue ()
  • boolsk getValueIsAdjusting ()
  • int getExtent ()
  • ugyldigt sætMaksimum (int)
  • ugyldigt sætMinimum (int)
  • ugyldigt setValue (int)
  • void setValueIsAdjusting (boolean)
  • ugyldigt setExtent (int)
  • void setRangeProperties (int værdi, int omfang, int min, int max, boolsk justering)
  • ugyldig addChangeListener (ChangeListener)
  • ugyldig removeChangeListener (ChangeListener)

Som de to sidste ovennævnte metoder angiver, forekomster af DefaultBoundedRangeModel () støtte lyttere til forandring. Eksempel 1 viser, hvordan applikationen udnytter denne funktion:

Eksempel 1. To observatører reagerer på modelændringer

import javax.swing. *; import javax.swing.event. *; import java.awt. *; import java.awt.event. *; importer java.util. *; offentlig klassetest udvider JFrame { privat DefaultBoundedRangeModel model = ny DefaultBoundedRangeModel (100,0,0,100); privat JSlider skyder = ny JSlider (model); privat JLabel readOut = nyt JLabel ("100%"); privat ImageIcon image = nyt ImageIcon ("shortcake.jpg"); privat ImageView imageView = nyt ImageView (billede, model); public Test () {super ("Observatørdesignmønsteret"); Container contentPane = getContentPane (); JPanel-panel = nyt JPanel (); panel.add (nyt JLabel ("Indstil billedstørrelse:")); panel.add (skyder); panel.add (readOut); contentPane.add (panel, BorderLayout.NORTH); contentPane.add (imageView, BorderLayout.CENTER); model.addChangeListener (ny ReadOutSynchronizer ()); } offentlig statisk ugyldig hoved (String args []) {Test test = new Test (); test.setBounds (100.100.400.350); test.show (); } klasse ReadOutSynchronizer implementerer ChangeListener {offentligt ugyldigt stateChanged(ChangeEvent e) {String s = Integer.toString (model.getValue ()); readOut.setText (s + "%"); readOut.revalidate (); }}} klasse ImageView udvider JScrollPane {privat JPanel-panel = nyt JPanel (); privat dimension originalSize = ny dimension (); privat billede originalImage; privat ImageIcon-ikon; offentlig ImageView (ImageIcon-ikon, BoundedRangeModel-model) {panel.setLayout (ny BorderLayout ()); panel.add (nyt JLabel (ikon)); this.icon = ikon; this.originalImage = icon.getImage (); setViewportView (panel); model.addChangeListener (ny ModelListener ()); originalSize.width = icon.getIconWidth (); originalSize.height = icon.getIconHeight (); } klasse ModelListener implementerer ChangeListener {offentligt ugyldigt stateChanged(ChangeEvent e) {BoundedRangeModel model = (BoundedRangeModel)e.getSource (); hvis (model.getValueIsAdjusting ()) {int min = model.getMinimum (), max = model.getMaximum (), span = max - min, værdi = model.getValue (); dobbelt multiplikator = (dobbelt) værdi / (dobbelt) span; multiplikator = multiplikator == 0,0? 0,01: multiplikator; Billede skaleret = originalImage.getScaledInstance ((int) (originalSize.width * multiplikator), (int) (originalSize.højde * multiplikator), Image.SCALE_FAST); icon.setImage (skaleret); panel.revalidate (); panel.repaint (); }}}} 

Når du bevæger skyderknappen, ændrer skyderen modelens værdi. Denne ændring udløser hændelsesmeddelelser til de to ændringslyttere, der er registreret med modellen, som justerer aflæsningen og skalerer billedet. Begge lyttere bruger den ændringsbegivenhed, der er videregivet til

stateChanged ()

for at bestemme modelens nye værdi.

Swing er en stor bruger af Observer-mønsteret - det implementerer mere end 50 begivenhedslyttere til implementering af applikationsspecifik adfærd, fra at reagere på en presset knap til at nedlægge veto mod en vindueslukningshændelse for en intern ramme. Men Swing er ikke den eneste ramme, der udnytter Observer-mønsteret godt - det er meget brugt i Java 2 SDK; for eksempel: Abstract Window Toolkit, JavaBeans-rammen, javax. navngivning pakke- og input / output-håndterere.

Eksempel 1 viser specifikt brugen af ​​Observer-mønsteret med Swing. Inden vi diskuterer flere observatørmønsterdetaljer, lad os se på, hvordan mønsteret generelt implementeres.

Sådan fungerer observatørmønsteret

Figur 2 viser, hvordan objekter i observatørmønsteret er relateret.

Emnet, som er en begivenhedskilde, vedligeholder en samling observatører og giver metoder til at tilføje og fjerne observatører fra denne samling. Emnet implementerer også a underrette() metode, der giver hver registreret observatør besked om begivenheder, der interesserer observatøren. Emner underretter observatører ved at påkalde observatørens opdater () metode.

Figur 3 viser et sekvensdiagram for observatørmønsteret.

Typisk vil et ikke-relateret objekt påberåbe sig et motivs metode, der ændrer motivets tilstand. Når det sker, påberåber emnet sig sit eget underrette() metode, som gentager sig over samlingen af ​​observatører, kalder hver observatørs opdater () metode.

Observer-mønsteret er et af de mest grundlæggende designmønstre, fordi det giver meget afkoblede objekter mulighed for at kommunikere. I eksempel 1 er det eneste, den afgrænsede rækkevandsmodel ved om sine lyttere, at de implementerer en stateChanged () metode. Lytterne er kun interesserede i modelens værdi, ikke hvordan modellen implementeres. Modellen og dens lyttere ved meget lidt om hinanden, men takket være Observer-mønsteret kan de kommunikere. Den høje grad af afkobling mellem modeller og lyttere giver dig mulighed for at opbygge software sammensat af tilslutningsbare objekter, hvilket gør din kode meget fleksibel og genanvendelig.

Java 2 SDK og observatørmønsteret

Java 2 SDK giver en klassisk implementering af observatørmønsteret med Observer interface og Observerbar klasse fra java.util vejviser. Det Observerbar klasse repræsenterer emnet; observatører implementerer Observer interface. Interessant nok bruges denne klassiske implementering af Observer-mønster sjældent i praksis, fordi det kræver, at emner udvider Observerbar klasse. At kræve arv i dette tilfælde er et dårligt design, fordi potentielt enhver form for objekt er en emnekandidat, og fordi Java ikke understøtter flere arv; ofte har disse fagkandidater allerede en superklasse.

Den hændelsesbaserede implementering af Observer-mønsteret, som blev brugt i det foregående eksempel, er det overvældende valg til implementering af Observer-mønster, fordi det ikke kræver, at emner udvider en bestemt klasse. I stedet følger emner en konvention, der kræver følgende offentlige lytterregistreringsmetoder:

  • ugyldig addXXXListener (XXXListener)
  • annullere fjerneXXXListener (XXXListener)

Når et motiv er bundet ejendom (en egenskab der er blevet observeret af lyttere) ændringer, emnet itererer over sine lyttere og påberåber sig metoden defineret af XXXListener interface.

Nu skal du have en god forståelse af observatørmønsteret. Resten af ​​denne artikel fokuserer på nogle af observatørmønstrets finere punkter.

Anonyme indre klasser

I eksempel 1 brugte jeg indre klasser til at implementere applikationens lyttere, fordi lytterklasserne var tæt koblet til deres lukkende klasse; dog kan du implementere lyttere som du ønsker. Et af de mest populære valg til håndtering af brugergrænsefladehændelser er den anonyme indre klasse, som er en klasse uden navn, der er oprettet in-line, som vist i eksempel 2:

Eksempel 2. Implementere observatører med anonyme indre klasser

... offentlig klassetest udvider JFrame {... offentlig test () {... model.addChangeListener (ny ChangeListener () {public void stateChanged (ChangeEvent e) {String s = Integer.toString (model.getValue ()); readOut.setText (s + "%"); readOut.revalidate (); }}); } ...} klasse ImageView udvider JScrollPane {... public ImageView (endelig ImageIcon-ikon, BoundedRangeModel-model) {... model.addChangeListener (ny ChangeListener () {public void stateChanged (ChangeEvent e) {BoundedRangeModel model = (BoundedRangeModel)e.getSource (); hvis (model.getValueIsAdjusting ()) {int min = model.getMinimum (), max = model.getMaximum (), span = max - min, værdi = model.getValue (); dobbelt multiplikator = (dobbelt) værdi / (dobbelt) span; multiplikator = multiplikator == 0,0? 0,01: multiplikator; Billed skaleret = originalImage.getScaledInstance ((int) (originalSize.width * multiplikator), (int) (originalSize.højde * multiplikator), Image.SCALE_FAST); icon.setImage (skaleret); panel.revalidate (); }}}); }} 

Eksempel 2's kode svarer funktionelt til eksempel 1's kode; koden ovenfor bruger dog anonyme indre klasser til at definere klassen og oprette en instans i et øjeblik.

JavaBeans begivenhedshåndterer

Brug af anonyme indre klasser som vist i det foregående eksempel var meget populær hos udviklere, så startende med Java 2 Platform, Standard Edition (J2SE) 1.4, har JavaBeans-specifikationen taget ansvaret for at implementere og instantiere disse indre klasser for dig med EventHandler klasse, som vist i eksempel 3:

Eksempel 3. Brug af java.beans.EventHandler

importere java.bønner.EventHandler; ... offentlig klassetest udvider JFrame {... offentlig test () {... model.addChangeListener (EventHandler.create (ChangeListener.class, dette, "updateReadout")); } ... offentlig ugyldighed updateReadout () {String s = Integer.toString (model.getValue ()); readOut.setText (s + "%"); readOut.revalidate (); }} ...