Pogled promatrača iznutra

Nedugo mi je otkazala spojka, pa sam svoj Jeep odvukao do lokalnog zastupstva. U salonu nisam nikoga poznavao i nitko me nije poznavao, pa sam im dao svoj telefonski broj kako bi me mogli obavijestiti s procjenom. Taj je aranžman uspio tako dobro da smo i mi učinili istu stvar kad je posao bio gotov. Budući da se sve ovo za mene pokazalo savršeno, pretpostavljam da servisni odjel zastupništva koristi isti obrazac kod većine svojih kupaca.

Ovaj obrazac objavljivanje-pretplata, gdje se promatrač registrira s subjektom i naknadno prima obavijesti , prilično je uobičajen u svakodnevnom životu i u virtualnom svijetu razvoja softvera. U stvari, obrazac Promatrač , kao što je poznat, jedna je od vezica objektno orijentiranog razvoja softvera, jer omogućuje komuniciranje različitih objekata. Ta sposobnost omogućuje vam uključivanje predmeta u okvir tijekom izvođenja, što omogućuje vrlo fleksibilan, proširiv i ponovni softver.

Napomena: Izvorni kôd ovog članka možete preuzeti iz izvora.

Uzorak promatrača

U Dizajn uzorcima , autori opisuju obrazac Promatrača ovako:

Definirajte ovisnost između jednog i mnogih objekata tako da, kada jedan objekt promijeni stanje, svi njegovi ovisnici budu automatski obaviješteni i ažurirani.

Uzorak Promatrača ima jednog subjekta i potencijalno mnogo promatrača. Promatrači se registriraju kod subjekta koji obavještava promatrače kad se dogode događaji. Primjer prototipskog promatrača je grafičko korisničko sučelje (GUI) koje istovremeno prikazuje dva prikaza jednog modela; pogledi se registriraju s modelom, a kada se model promijeni, obavještava poglede koji se u skladu s tim ažuriraju. Pogledajmo kako to funkcionira.

Promatrači u akciji

Aplikacija prikazana na slici 1 sadrži jedan model i dva prikaza. Vrijednošću modela, koja predstavlja uvećanje slike, manipulira se pomicanjem gumba klizača. Prikazi, poznati kao komponente u Swingu, oznaka su koja pokazuje vrijednost modela i okno za pomicanje koje skalira sliku u skladu s vrijednošću modela.

Model u aplikaciji je instanca DefaultBoundedRangeModel()koja prati ograničenu cijelu vrijednost - u ovom slučaju od 0do 100- ovim metodama:

  • int getMaximum()
  • int getMinimum()
  • int getValue()
  • boolean getValueIsAdjusting()
  • int getExtent()
  • void setMaximum(int)
  • void setMinimum(int)
  • void setValue(int)
  • void setValueIsAdjusting(boolean)
  • void setExtent(int)
  • void setRangeProperties(int value, int extent, int min, int max, boolean adjusting)
  • void addChangeListener(ChangeListener)
  • void removeChangeListener(ChangeListener)

Kao što pokazuju dvije posljednje gore navedene metode, slučajevi DefaultBoundedRangeModel()podrške mijenjaju slušatelje. Primjer 1 pokazuje kako aplikacija iskorištava tu značajku:

Primjer 1. Dva promatrača reagiraju na promjene modela

import javax.swing. *; uvoz javax.swing.event. *; import java.awt. *; import java.awt.event. *; uvoz java.util. *; test javne klase proširuje JFrame { private DefaultBoundedRangeModel model = new DefaultBoundedRangeModel (100,0,0,100); privatni klizač JSlider = novi JSlider ( model ); private JLabel readOut = novi JLabel ("100%"); private ImageIcon image = novi ImageIcon ("shortcake.jpg"); private ImageView imageView = novi ImageView (slika, model); javni test () {super ("Uzorak dizajna promatrača"); Sadržaj spremnikaPane = getContentPane (); JPanel ploča = novi JPanel (); panel.add (novi JLabel ("Postavi veličinu slike:")); panel.add (klizač); panel.add (readOut); contentPane.add (ploča, BorderLayout.NORTH); contentPane.add (imageView, BorderLayout.CENTER);model.addChangeListener (novi ReadOutSynchronizer ()); } public static void main (String args []) {Test test = new Test (); test.setBounds (100,100,400,350); test.show (); } klasa ReadOutSynchronizer implementira ChangeListener {public void stateChanged (ChangeEvent e) {String s = Integer.toString (model.getValue ()); readOut.setText (s + "%"); readOut.revalidate (); }}} klasa ImageView proširuje JScrollPane {privatni JPanel panel = novi JPanel (); private Dimension originalSize = nova dimenzija (); privatna slika originalImage; privatna ikona ImageIcon; javni ImageView (ikona ImageIcon, model BoundedRangeModel) {panel.setLayout (novi BorderLayout ()); panel.add (novi JLabel (ikona)); this.icon = ikona; this.originalImage = icon.getImage (); setViewportView (ploča);model.addChangeListener (novi ModelListener ()); originalSize.width = icon.getIconWidth (); originalSize.height = icon.getIconHeight (); } klasa ModelListener implementira ChangeListener {public void stateChanged (ChangeEvent e) {BoundedRangeModel model = (BoundedRangeModel) e.getSource () ; if (model.getValueIsAdjusting ()) {int min = model.getMinimum (), max = model.getMaximum (), span = max - min, value = model.getValue (); dvostruki množitelj = (dvostruka) vrijednost / (dvostruki) raspon; množitelj = množitelj == 0,0? 0,01: množitelj; Slika prilagođena = originalImage.getScaledInstance ((int) (množitelj originalSize.width *), (int) (množitelj originalSize.height *), Image.SCALE_FAST); icon.setImage (skalirano); panel.revalidate (); panel.repaint (); }}}}

Kad pomaknete gumb klizača, klizač mijenja vrijednost svog modela. Ta promjena pokreće obavijesti o događajima dva preslušača promjena registrirana u modelu, koji prilagođavaju očitanje i skaliraju sliku. Oba slušatelja koriste događaj promjene kojem su proslijedili

stateChanged()

kako bi se utvrdila nova vrijednost modela.

Swing je teški korisnik Observer uzorka - implementira više od 50 slušatelja događaja za provođenje ponašanja specifičnog za aplikaciju, od reakcije na pritisnuti gumb do stavljanja veta na događaj zatvaranja prozora za unutarnji okvir. Ali Swing nije jedini okvir koji dobro koristi Observer obrazac - on se široko koristi u Java 2 SDK; na primjer: Abstract Window Toolkit, JavaBeans okvir, javax.namingpaket i ulazno / izlazni rukovatelji.

Primjer 1 posebno prikazuje upotrebu Observer uzorka s Swingom. Prije nego što razgovaramo o više detalja uzorka Observer, pogledajmo kako se obrazac općenito implementira.

Kako funkcionira obrazac Promatrač

Slika 2 pokazuje kako su objekti u uzorku Observer povezani.

Subjekt, koji je izvor događaja, održava kolekciju promatrača i pruža metode za dodavanje i uklanjanje promatrača iz te zbirke. Ispitanik također primjenjuje notify()metodu koja obavještava svakog registriranog promatrača o događajima koji ga promatraju. Ispitanici obavještavaju promatrače pozivanjem na promatračevu update()metodu.

Na slici 3 prikazan je dijagram sekvenci za obrazac Promatrač.

Tipično će neki nepovezani objekt pozvati metodu subjekta koja mijenja stanje subjekta. Kada se to dogodi, subjekt poziva svoju vlastitu notify()metodu koja se ponavlja kroz prikupljanje promatrača, pozivajući update()metodu svakog promatrača .

Uzorak promatrača jedan je od najvažnijih temeljnih dizajnerskih uzoraka jer omogućuje komunikaciju jako nevezanih objekata. U Primjeru 1, jedino što model s ograničenim opsegom zna o svojim slušateljima jest da oni implementiraju stateChanged()metodu. Slušatelje zanima samo vrijednost modela, a ne kako se model implementira. Model i njegovi slušatelji vrlo malo znaju jedni o drugima, ali zahvaljujući obrascu Observer mogu komunicirati. Taj visoki stupanj razdvajanja između modela i slušatelja omogućuje vam izradu softvera koji se sastoji od priključnih objekata, čineći vaš kod vrlo fleksibilnim i ponovnim za upotrebu.

Java 2 SDK i obrazac Observer

Java 2 SDK pruža klasičnu implementaciju uzorka Observer sa Observersučeljem i Observableklasom iz java.utildirektorija. Predavanje Observablepredstavlja predmet; promatrači implementiraju Observersučelje. Zanimljivo je da se ova klasična primjena uzorka Observer rijetko koristi u praksi jer zahtijeva da subjekti prošire Observablerazred. Zahtjev za nasljeđivanjem u ovom je slučaju loš dizajn jer je potencijalno bilo koja vrsta objekta kandidat za predmet i zato što Java ne podržava višestruko nasljeđivanje; često ti predmetni kandidati već imaju superklasu.

Implementacija uzorka Observer temeljena na događajima, koja je korištena u prethodnom primjeru, neodoljiv je izbor za provedbu uzorka Observer jer ne zahtijeva od subjekata da prošire određenu klasu. Umjesto toga, ispitanici slijede konvenciju koja zahtijeva sljedeće metode registracije javnog slušatelja:

  • void addXXXListener(XXXListener)
  • void removeXXXListener(XXXListener)

Kad god se promijeni svojstvo vezanog za subjekt (svojstvo koje su promatrali slušatelji), subjekt se prevlači preko svojih slušatelja i poziva metodu definiranu XXXListenersučeljem.

Do sada biste trebali dobro shvatiti obrazac Promatrača. Ostatak ovog članka usredotočen je na neke fine točke Observerovog uzorka.

Anonimni unutarnji časovi

In Example 1, I used inner classes to implement the application's listeners, because the listener classes were tightly coupled to their enclosing class; however, you can implement listeners any way you desire. One of the most popular choices for handling user interface events is the anonymous inner class, which is a class with no name that's created in-line, as demonstrated in Example 2:

Example 2. Implement observers with anonymous inner classes

... public class Test extends JFrame { ... public Test() { ... model.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { String s = Integer.toString(model.getValue()); readOut.setText(s + "%"); readOut.revalidate(); } }); } ... } class ImageView extends JScrollPane { ... public ImageView(final ImageIcon icon, BoundedRangeModel model) { ... model.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { BoundedRangeModel model = (BoundedRangeModel)e.getSource(); if(model.getValueIsAdjusting()) { int min = model.getMinimum(), max = model.getMaximum(), span = max - min, value = model.getValue(); double multiplier = (double)value / (double)span; multiplier = multiplier == 0.0 ? 0.01 : multiplier; Image scaled = originalImage.getScaledInstance( (int)(originalSize.width * multiplier), (int)(originalSize.height * multiplier), Image.SCALE_FAST); icon.setImage(scaled); panel.revalidate(); } } }); } } 

Example 2's code is functionally equivalent to Example 1's code; however, the code above uses anonymous inner classes to define the class and create an instance in one fell swoop.

JavaBeans event handler

Korištenje anonimnih unutarnjih klasa kao što je prikazano u prethodnom primjeru bilo je vrlo popularno među programerima, pa je, počevši od Java 2 Platform, Standard Edition (J2SE) 1.4, specifikacija JavaBeans preuzela odgovornost za implementaciju i instanciranje tih unutarnjih klasa umjesto vas s EventHandlerklasom, kako je prikazano u primjeru 3:

Primjer 3. Korištenje java.beans.EventHandler

uvoz java.beans.EventHandler; ... Test javne klase proširuje JFrame {... public Test () {... model.addChangeListener (EventHandler.create (ChangeListener.class, this, "updateReadout")); } ... javna void updateReadout () {Niz s = Integer.toString (model.getValue ()); readOut.setText (s + "%"); readOut.revalidate (); }} ...