Programování

Pohled zevnitř na Observer

Není to tak dávno, co moje spojka vypadla, a tak jsem nechal Jeep odtáhnout k místnímu prodejci. V obchodním zastoupení jsem nikoho neznal a nikdo z nich mě neznal, proto jsem jim dal své telefonní číslo, aby mě mohli informovat odhadem. Toto uspořádání fungovalo tak dobře, že jsme totéž udělali, když byla práce dokončena. Vzhledem k tomu, že se to všechno pro mě ukázalo perfektně, mám podezření, že servisní oddělení v obchodním zastoupení používá stejný vzor u většiny svých zákazníků.

Tento vzor publikování a odběru, kde pozorovatel registruje se a předmět a následně přijímá oznámení, je docela běžné, a to jak v každodenním životě, tak ve virtuálním světě vývoje softwaru. Ve skutečnosti Pozorovatel pattern, jak je známo, je jedním ze základních pilířů objektově orientovaného vývoje softwaru, protože umožňuje komunikaci odlišných objektů. Tato schopnost umožňuje za běhu připojit objekty do rámce, což umožňuje vysoce flexibilní, rozšiřitelný a opakovaně použitelný software.

Poznámka: Zdrojový kód tohoto článku si můžete stáhnout ze zdrojů.

Vzor pozorovatele

v Designové vzory, autoři popisují vzor Observer takto:

Definujte závislost jeden na mnoho mezi objekty tak, aby při změně stavu jednoho objektu byly všechny jeho závislé osoby informovány a automaticky aktualizovány.

Vzor pozorovatele má jeden předmět a potenciálně mnoho pozorovatelů. Pozorovatelé se zaregistrují u subjektu, který je upozorní, když dojde k události. Příkladem prototypu Observer je grafické uživatelské rozhraní (GUI), které současně zobrazuje dva pohledy na jeden model; pohledy se zaregistrují u modelu a když se model změní, upozorní pohledy, které se odpovídajícím způsobem aktualizují. Podívejme se, jak to funguje.

Pozorovatelé v akci

Aplikace zobrazená na obrázku 1 obsahuje jeden model a dva pohledy. S hodnotou modelu, která představuje zvětšení obrazu, se manipuluje pohybem posuvného knoflíku. Pohledy, známé jako komponenty ve Swingu, jsou štítkem, který zobrazuje hodnotu modelu, a rolovacím podoknem, které mění velikost obrázku v souladu s hodnotou modelu.

Model v aplikaci je instancí DefaultBoundedRangeModel (), který sleduje omezenou celočíselnou hodnotu - v tomto případě od 0 na 100—S těmito metodami:

  • 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 hodnota, int rozsah, int min, int max, boolean úprava)
  • void addChangeListener (ChangeListener)
  • void removeChangeListener (ChangeListener)

Jak ukazují poslední dvě metody uvedené výše, instance DefaultBoundedRangeModel () podporovat posluchače změn. Příklad 1 ukazuje, jak aplikace využívá tuto funkci:

Příklad 1. Dva pozorovatelé reagují na změny modelu

importovat javax.swing. *; importovat javax.swing.event. *; importovat java.awt. *; importovat java.awt.event. *; import java.util. *; test veřejné třídy rozšiřuje JFrame { soukromý model DefaultBoundedRangeModel = nový DefaultBoundedRangeModel (100,0,0,100); soukromý posuvník JSlider = nový JSlider (Modelka); soukromá JLabel readOut = nová JLabel ("100%"); private ImageIcon image = new ImageIcon ("shortcake.jpg"); soukromý ImageView imageView = nový ImageView (obrázek, model); public Test () {super ("Návrhový vzor pozorovatele"); Kontejner contentPane = getContentPane (); Panel JPanel = nový JPanel (); panel.add (nový JLabel ("Nastavit velikost obrázku:")); panel.add (posuvník); panel.add (readOut); contentPane.add (panel, BorderLayout.NORTH); contentPane.add (imageView, BorderLayout.CENTER); model.addChangeListener (nový ReadOutSynchronizer ()); } public static void main (String args []) {Test test = new Test (); test.setBounds (100,100,400,350); test.show (); } třída ReadOutSynchronizer implementuje ChangeListener {public void stateChanged(ChangeEvent e) {String s = Integer.toString (model.getValue ()); readOut.setText (s + "%"); readOut.revalidate (); }}} třída ImageView rozšiřuje JScrollPane {soukromý panel JPanel = nový JPanel (); soukromá dimenze originalSize = nová dimenze (); soukromý obrázek originalImage; soukromá ikona ImageIcon; public ImageView (ikona ImageIcon, model BoundedRangeModel) {panel.setLayout (nový BorderLayout ()); panel.add (nový JLabel (ikona)); this.icon = ikona; this.originalImage = ikona.getImage (); setViewportView (panel); model.addChangeListener (nový ModelListener ()); originalSize.width = ikona.getIconWidth (); originalSize.height = ikona.getIconHeight (); } třída ModelListener implementuje ChangeListener {public void stateChanged(ChangeEvent e) {BoundedRangeModel model = (BoundedRangeModel)e.getSource (); if (model.getValueIsAdjusting ()) {int min = model.getMinimum (), max = model.getMaximum (), rozpětí = max - min, hodnota = model.getValue (); double multiplier = (double) value / (double) span; multiplikátor = multiplikátor == 0,0? 0,01: multiplikátor; Škálovaný obrázek = originalImage.getScaledInstance ((int) (originalSize.width * multiplier), (int) (originalSize.height * multiplier), Image.SCALE_FAST); icon.setImage (v měřítku); panel.revalidate (); panel.repaint (); }}}} 

Když pohnete posuvníkem, posuvník změní hodnotu svého modelu. Tato změna aktivuje upozornění na události dvěma posluchačům změn registrovaným u modelu, kteří upravují odečet a mění měřítko obrazu. Oba posluchači používají událost změny předanou

stateChanged ()

k určení nové hodnoty modelu.

Swing je velkým uživatelem vzoru Observer - implementuje více než 50 posluchačů událostí pro implementaci chování specifického pro aplikaci, od reakce na stisknuté tlačítko až po vetování události zavření okna pro vnitřní rámec. Swing však není jediný rámec, který dobře využívá vzor Observer - je široce používán v sadě Java 2 SDK; například: Sada nástrojů pro abstraktní okna, rámec JavaBeans, javax.naming balíček a manipulátory vstupu / výstupu.

Příklad 1 konkrétně ukazuje použití vzoru Observer s Swingem. Než probereme další podrobnosti vzoru pozorovatele, podívejme se, jak je vzor obecně implementován.

Jak funguje vzor pozorovatele

Obrázek 2 ukazuje, jak jsou objekty ve vzoru Observer příbuzné.

Subjekt, který je zdrojem události, udržuje sbírku pozorovatelů a poskytuje metody pro přidání a odebrání pozorovatelů z této sbírky. Předmět také implementuje a oznámit() metoda, která upozorňuje každého registrovaného pozorovatele na události, které ho zajímají. Subjekty upozorní pozorovatele vyvoláním pozorovatele Aktualizace() metoda.

Obrázek 3 ukazuje sekvenční diagram vzoru Observer.

Typicky nějaký nesouvisející objekt vyvolá metodu subjektu, která upravuje stav subjektu. Když k tomu dojde, subjekt vyvolá své vlastní oznámit() metoda, která iteruje nad kolekcí pozorovatelů, volajících každého pozorovatele Aktualizace() metoda.

Vzor Observer je jedním z nejzákladnějších návrhových vzorů, protože umožňuje komunikaci vysoce oddělených objektů. V příkladu 1 jediná věc, kterou model omezeného rozsahu ví o svých posluchačech, je to, že implementují a stateChanged () metoda. Posluchače zajímá pouze hodnota modelu, nikoli způsob implementace modelu. Model a jeho posluchači o sobě navzájem vědí jen velmi málo, ale díky vzoru Observer mohou komunikovat. Tento vysoký stupeň oddělení mezi modely a posluchači vám umožní vytvářet software složený z připojitelných objektů, díky čemuž bude váš kód vysoce flexibilní a znovu použitelný.

Sada Java 2 SDK a vzor Observer

Sada Java 2 SDK poskytuje klasickou implementaci vzoru Observer s Pozorovatel rozhraní a Pozorovatelný třída z java.util adresář. The Pozorovatelný třída představuje předmět; pozorovatelé provádějí Pozorovatel rozhraní. Je zajímavé, že tato klasická implementace vzoru pozorovatele se v praxi používá jen zřídka, protože vyžaduje, aby subjekty rozšířily Pozorovatelný třída. Vyžadování dědičnosti je v tomto případě špatný design, protože potenciálním kandidátem předmětu je potenciálně jakýkoli typ objektu a protože Java nepodporuje vícenásobnou dědičnost; tito předmětoví kandidáti často již mají nadtřídu.

Implementace vzoru Observer založená na událostech, která byla použita v předchozím příkladu, je ohromnou volbou pro implementaci vzoru Observer, protože nevyžaduje předměty k rozšíření konkrétní třídy. Subjekty místo toho dodržují konvenci, která vyžaduje následující metody registrace veřejného posluchače:

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

Kdykoli subjekt vázaný majetek (vlastnost, kterou posluchači pozorovali) se změní, předmět iteruje přes své posluchače a vyvolá metodu definovanou XXXListener rozhraní.

Nyní byste měli dobře pochopit vzor pozorovatele. Zbytek tohoto článku se zaměřuje na některé z jemnějších bodů vzoru Observer.

Anonymní vnitřní třídy

V příkladu 1 jsem použil vnitřní třídy k implementaci posluchačů aplikace, protože třídy posluchače byly pevně spojeny s jejich uzavírající třídou; posluchače však můžete implementovat libovolným způsobem. Jednou z nejpopulárnějších možností zpracování událostí uživatelského rozhraní je anonymní vnitřní třída, což je třída bez názvu, která je vytvořena in-line, jak je ukázáno v příkladu 2:

Příklad 2. Implementujte pozorovatele s anonymními vnitřními třídami

... test veřejné třídy rozšiřuje JFrame {... public Test () {... model.addChangeListener (nový ChangeListener () {public void stateChanged (ChangeEvent e) {String s = Integer.toString (model.getValue ()); readOut.setText (s + "%"); readOut.revalidate (); }}); } ...} třída ImageView rozšiřuje JScrollPane {... veřejný ImageView (konečná ikona ImageIcon, model BoundedRangeModel) {... model.addChangeListener (nový ChangeListener () {public void stateChanged (ChangeEvent e) {BoundedRangeModel model = (BoundedRangeModel)e.getSource (); if (model.getValueIsAdjusting ()) {int min = model.getMinimum (), max = model.getMaximum (), rozpětí = max - min, hodnota = model.getValue (); double multiplier = (double) value / (double) span; multiplikátor = multiplikátor == 0,0? 0,01: multiplikátor; Škálovaný obrázek = originalImage.getScaledInstance ((int) (originalSize.width * multiplier), (int) (originalSize.height * multiplier), Image.SCALE_FAST); icon.setImage (v měřítku); panel.revalidate (); }}}); }} 

Kód z příkladu 2 je funkčně ekvivalentní s kódem z příkladu 1; výše uvedený kód však používá anonymní vnitřní třídy k definování třídy a vytvoření instance jedním tahem.

Obslužná rutina události JavaBeans

Používání anonymních vnitřních tříd, jak je znázorněno v předchozím příkladu, bylo u vývojářů velmi oblíbené, takže počínaje platformou Java 2 Platform, Standard Edition (J2SE) 1.4 převzala specifikace JavaBeans odpovědnost za implementaci a instanci těchto vnitřních tříd za vás EventHandler třída, jak je ukázáno v příkladu 3:

Příklad 3. Použití java.beans.EventHandler

import java.beans.EventHandler; ... test veřejné třídy rozšiřuje JFrame {... public Test () {... model.addChangeListener (EventHandler.create (ChangeListener.class, this, "updateReadout")); } ... veřejná prázdnota updateReadout () {String s = Integer.toString (model.getValue ()); readOut.setText (s + "%"); readOut.revalidate (); }} ... 

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