Programmering

En innvendig utsikt over Observer

For ikke lenge siden ga clutchen meg, så jeg fikk jeepen min slept til en lokal forhandler. Jeg kjente ingen på forhandleren, og ingen av dem kjente meg, så jeg ga dem telefonnummeret mitt slik at de kunne varsle meg med et estimat. Den ordningen fungerte så bra at vi gjorde det samme når arbeidet var ferdig. Fordi alt dette ble perfekt for meg, mistenker jeg at serviceavdelingen hos forhandleren bruker det samme mønsteret med de fleste av sine kunder.

Dette publiser-abonnementsmønsteret, hvor en observatør registrerer seg med en Emne og mottar deretter varsler, er ganske vanlig, både i hverdagen og i den virtuelle verdenen av programvareutvikling. Faktisk, den Observatør mønster, som det er kjent, er en av bøylene for objektorientert programvareutvikling fordi det lar forskjellige objekter kommunisere. Denne muligheten lar deg koble objekter til et rammeverk ved kjøretid, noe som gir mulighet for svært fleksibel, utvidbar og gjenbrukbar programvare.

Merk: Du kan laste ned kildekoden til denne artikkelen fra Resources.

Observer-mønsteret

I Design mønstre, beskriver forfatterne observatørmønsteret slik:

Definere en til mange avhengighet mellom objekter slik at når et objekt endrer tilstand, blir alle dets avhengige varslet og oppdatert automatisk.

Observer-mønsteret har ett emne og potensielt mange observatører. Observatører registrerer seg med motivet, som gir observatørene beskjed når hendelser inntreffer. Det prototypiske observatøreksemplet er et grafisk brukergrensesnitt (GUI) som samtidig viser to visninger av en enkelt modell; visningene registreres med modellen, og når modellen endres, gir den beskjed om visningene, som oppdateres deretter. La oss se hvordan det fungerer.

Observatører i aksjon

Applikasjonen vist i figur 1 inneholder en modell og to visninger. Modellens verdi, som representerer forstørrelse av bildet, manipuleres ved å bevege skyveknappen. Visningene, kjent som komponenter i Swing, er en etikett som viser modellens verdi og en rullefelt som skalerer et bilde i samsvar med modellens verdi.

Modellen i applikasjonen er en forekomst av DefaultBoundedRangeModel (), som sporer en avgrenset heltallverdi — i dette tilfellet fra 0 til 100—Med disse metodene:

  • int getMaximum ()
  • int getMinimum ()
  • int getValue ()
  • boolsk getValueIsAdjusting ()
  • int getExtent ()
  • void setMaximum (int)
  • void setMinimum (int)
  • ugyldig setValue (int)
  • void setValueIsAdjusting (boolean)
  • void setExtent (int)
  • void setRangeProperties (int-verdi, int-omfang, int min, int max, boolsk justering)
  • void addChangeListener (ChangeListener)
  • ugyldig removeChangeListener (ChangeListener)

Som de to siste metodene som er oppført ovenfor, indikerer forekomster av DefaultBoundedRangeModel () støtte endringslyttere. Eksempel 1 viser hvordan applikasjonen utnytter den funksjonen:

Eksempel 1. To observatører reagerer på modellendringer

import javax.swing. *; importere javax.swing.event. *; importer java.awt. *; importer java.awt.event. *; importer java.util. *; offentlig klassetest utvider JFrame { privat DefaultBoundedRangeModel-modell = ny DefaultBoundedRangeModel (100,0,0,100); privat JSlider-glidebryter = ny JSlider (modell); privat JLabel readOut = ny JLabel ("100%"); privat ImageIcon image = nytt ImageIcon ("shortcake.jpg"); privat ImageView imageView = nytt ImageView (bilde, modell); public Test () {super ("The Observer Design Pattern"); Container contentPane = getContentPane (); JPanel panel = ny JPanel (); panel.add (ny JLabel ("Angi bildestørrelse:")); panel.add (glidebryter); panel.add (readOut); contentPane.add (panel, BorderLayout.NORTH); contentPane.add (imageView, BorderLayout.CENTER); model.addChangeListener (ny ReadOutSynchronizer ()); } public static void main (String args []) {Test test = new Test (); test.setBounds (100,100,400,350); test.show (); } klasse ReadOutSynchronizer implementerer ChangeListener {offentlig ugyldig stateChanged(ChangeEvent e) {String s = Integer.toString (model.getValue ()); readOut.setText (s + "%"); readOut.revalidate (); }}} klasse ImageView utvider JScrollPane {privat JPanel-panel = nytt JPanel (); privat dimensjon originalSize = ny dimensjon (); privat bilde originalImage; privat ImageIcon-ikon; public ImageView (ImageIcon icon, BoundedRangeModel model) {panel.setLayout (new BorderLayout ()); panel.add (ny 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 {offentlig ugyldig stateChanged(ChangeEvent e) {BoundedRangeModel model = (BoundedRangeModel)e.getSource (); if (model.getValueIsAdjusting ()) {int min = model.getMinimum (), max = model.getMaximum (), span = max - min, value = model.getValue (); dobbel multiplikator = (dobbelt) verdi / (dobbelt) spenn; multiplikator = multiplikator == 0,0? 0,01: multiplikator; Bilde skalert = originalImage.getScaledInstance ((int) (originalSize.width * multiplier), (int) (originalSize.height * multiplier), Image.SCALE_FAST); icon.setImage (skalert); panel.revalidate (); panel.repaint (); }}}} 

Når du beveger glideknappen, endrer glidebryteren modellens verdi. Den endringen utløser hendelsesvarsler til de to endringslytterne som er registrert i modellen, som justerer avlesningen og skalerer bildet. Begge lytterne bruker endringshendelsen sendt til

stateChanged ()

for å bestemme modellens nye verdi.

Swing er en stor bruker av Observer-mønsteret - den implementerer mer enn 50 hendelseslyttere for å implementere applikasjonsspesifikk oppførsel, fra å reagere på en knapp som trykkes ned, til å nedlegge veto mot en hendelse som lukker vinduet for en intern ramme. Men Swing er ikke det eneste rammeverket som bruker Observer-mønsteret til god bruk - det er mye brukt i Java 2 SDK; for eksempel: Abstract Window Toolkit, JavaBeans framework, the javax.naming pakke, og input / output håndterere.

Eksempel 1 viser spesifikt bruk av Observer-mønsteret med Swing. Før vi diskuterer mer detaljer om observatørmønster, la oss se på hvordan mønsteret generelt implementeres.

Hvordan observatørmønsteret fungerer

Figur 2 viser hvordan objekter i Observer-mønsteret er relatert.

Emnet, som er en hendelseskilde, opprettholder en samling observatører og gir metoder for å legge til og fjerne observatører fra samlingen. Faget implementerer også a gi beskjed() metode som varsler hver registrerte observatør om hendelser som interesserer observatøren. Motiv varsler observatører ved å påkalle observatørens Oppdater() metode.

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

Vanligvis vil noe ikke-relatert objekt påkalle et motivs metode som endrer motivets tilstand. Når det skjer, påkaller subjektet sitt eget gi beskjed() metode, som gjentas over samlingen av observatører, og kaller hver observatørs Oppdater() metode.

Observer-mønsteret er et av de mest grunnleggende designmønstrene fordi det tillater svært frakoblede objekter å kommunisere. I eksempel 1 er det eneste modellen med begrenset rekkevidde vet om lytterne at de implementerer en stateChanged () metode. Lytterne er bare interessert i modellens verdi, ikke hvordan modellen implementeres. Modellen og dens lyttere vet veldig lite om hverandre, men takket være Observer-mønsteret kan de kommunisere. Den høye graden av frakobling mellom modeller og lyttere lar deg bygge programvare bestående av pluggbare gjenstander, noe som gjør koden din svært fleksibel og gjenbrukbar.

Java 2 SDK og observatørmønsteret

Java 2 SDK gir en klassisk implementering av Observer-mønsteret med Observatør grensesnitt og Observerbar klasse fra java.util katalog. De Observerbar klasse representerer faget; observatører implementerer Observatør grensesnitt. Interessant, denne klassiske implementeringen av Observer-mønster brukes sjelden i praksis fordi det krever at fagpersoner utvider Observerbar klasse. Kreving av arv i dette tilfellet er dårlig design fordi potensielt alle typer objekter er en emnekandidat, og fordi Java ikke støtter flere arv; ofte har fagkandidatene allerede en superklasse.

Den hendelsesbaserte implementeringen av Observer-mønsteret, som ble brukt i det foregående eksemplet, er det overveldende valget for Observer-mønsterimplementering fordi det ikke krever at fagene utvider en bestemt klasse. I stedet følger fagene en konvensjon som krever følgende offentlige registreringsmetoder for lytter:

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

Når et emne er det bundet eiendom (en egenskap som er observert av lyttere) endres, emnet itererer over lytterne og påkaller metoden definert av XXXListener grensesnitt.

Nå skal du ha god forståelse av Observer-mønsteret. Resten av denne artikkelen fokuserer på noen av Observer-mønsterets finere poeng.

Anonyme indre klasser

I eksempel 1 brukte jeg indre klasser for å implementere applikasjonens lyttere, fordi lytterklassene var tett koblet til deres vedlagte klasse; Imidlertid kan du implementere lyttere som du vil. Et av de mest populære valgene for håndtering av hendelser i brukergrensesnittet er den anonyme indre klassen, som er en klasse uten navn som er opprettet in-line, som vist i eksempel 2:

Eksempel 2. Implementere observatører med anonyme indre klasser

... public class Test utvider JFrame {... public Test () {... model.addChangeListener (ny ChangeListener () {public void stateChanged (ChangeEvent e) {String s = Integer.toString (model.getValue ()); readOut.setText (s + "%"); readOut.revalidate (); }}); } ...} klasse ImageView utvider JScrollPane {... public ImageView (endelig ImageIcon-ikon, BoundedRangeModel-modell) {... model.addChangeListener (ny ChangeListener () {public void stateChanged (ChangeEvent e) {BoundedRangeModel model = (BoundedRangeModel)e.getSource (); if (model.getValueIsAdjusting ()) {int min = model.getMinimum (), max = model.getMaximum (), span = max - min, verdi = model.getValue (); dobbel multiplikator = (dobbelt) verdi / (dobbelt) spenn; multiplikator = multiplikator == 0,0? 0,01: multiplikator; Bilde skalert = originalImage.getScaledInstance ((int) (originalSize.width * multiplikator), (int) (originalSize.height * multiplier), Image.SCALE_FAST); icon.setImage (skalert); panel.revalidate (); }}}); }} 

Eksempel 2s kode er funksjonelt ekvivalent med eksempel 1s kode; koden ovenfor bruker imidlertid anonyme indre klasser for å definere klassen og opprette en forekomst i ett slag.

JavaBeans hendelsesbehandler

Å bruke anonyme indre klasser som vist i forrige eksempel var veldig populært blant utviklere, så startende med Java 2 Platform, Standard Edition (J2SE) 1.4, har JavaBeans-spesifikasjonen tatt ansvaret for å implementere og instantiere disse indre klassene for deg EventHandler klasse, som vist i eksempel 3:

Eksempel 3. Bruke java.beans.EventHandler

importere java.bønner.EventHandler; ... public class Test utvider JFrame {... public Test () {... model.addChangeListener (EventHandler.create (ChangeListener.class, denne, "updateReadout")); } ... offentlig ugyldig updateReadout () {String s = Integer.toString (model.getValue ()); readOut.setText (s + "%"); readOut.revalidate (); }} ... 

$config[zx-auto] not found$config[zx-overlay] not found