Вътрешен изглед на Наблюдател

Не много отдавна моят съединител отказа, така че джипа ми беше теглен до местен дилър. Не познавах никой в ​​автокъщата и никой от тях не ме познаваше, затова им дадох телефонния си номер, за да могат да ме уведомят с приблизителна оценка. Това споразумение работи толкова добре, че направихме същото, когато работата приключи. Тъй като всичко се оказа перфектно за мен, подозирам, че сервизният отдел на дилърството използва същия модел с повечето си клиенти.

Този модел на публикуване-абониране, при който наблюдателят се регистрира с обект и впоследствие получава известия , е доста разпространен както в ежедневието, така и във виртуалния свят на разработването на софтуер. Всъщност моделът на Observer , както е известен, е един от връзките на обектно-ориентираното разработване на софтуер, защото позволява на различни обекти да комуникират. Тази способност ви позволява да включвате обекти в рамка по време на изпълнение, което позволява много гъвкав, разширяем и многократно използван софтуер.

Забележка: Можете да изтеглите изходния код на тази статия от ресурси.

Моделът на наблюдателя

В Design Patterns авторите описват модела на Observer така:

Дефинирайте зависимост от един до много между обектите, така че когато един обект променя състоянието, всички зависими да бъдат уведомявани и актуализирани автоматично.

Моделът на наблюдателя има един субект и потенциално много наблюдатели. Наблюдателите се регистрират при субекта, който уведомява наблюдателите, когато настъпят събития. Примерът за прототипен наблюдател е графичен потребителски интерфейс (GUI), който едновременно показва два изгледа на един модел; изгледите се регистрират с модела и когато моделът се промени, той уведомява за изгледите, които се актуализират съответно. Нека да видим как работи.

Наблюдатели в действие

Приложението, показано на фигура 1, съдържа един модел и два изгледа. Стойността на модела, която представлява увеличение на изображението, се манипулира чрез преместване на плъзгащото се копче. Изгледите, известни като компоненти в Swing, са етикет, който показва стойността на модела, и панел за превъртане, който мащабира изображение в съответствие със стойността на модела.

Моделът в приложението е екземпляр на DefaultBoundedRangeModel(), който проследява ограничена целочислена стойност - в случая от 0до 100- с тези методи:

  • 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)

Както показват последните два метода, изброени по-горе, случаи на DefaultBoundedRangeModel()поддръжка променят слушателите. Пример 1 показва как приложението се възползва от тази функция:

Пример 1. Двама наблюдатели реагират на промени в модела

импортиране на javax.swing. *; импортиране на javax.swing.event. *; импортиране на java.awt. *; импортиране на java.awt.event. *; импортиране на java.util. *; тест за публичен клас разширява JFrame { private DefaultBoundedRangeModel model = new DefaultBoundedRangeModel (100,0,0,100); частен слайдер JSlider = нов JSlider ( модел ); private JLabel readOut = new JLabel ("100%"); private ImageIcon image = new ImageIcon ("shortcake.jpg"); private ImageView imageView = нов ImageView (изображение, модел); публичен тест () {супер ("Моделът на дизайна на наблюдателя"); Container contentPane = getContentPane (); Панел JPanel = нов JPanel (); panel.add (нов JLabel ("Задаване на размер на изображението:")); panel.add (плъзгач); panel.add (readOut); contentPane.add (панел, BorderLayout.NORTH); contentPane.add (imageView, BorderLayout.CENTER);model.addChangeListener (нов ReadOutSynchronizer ()); } public static void main (String args []) {Test test = new Test (); test.setBounds (100 100 400 350); test.show (); } клас ReadOutSynchronizer реализира ChangeListener {public void stateChanged (ChangeEvent e) {String s = Integer.toString (model.getValue ()); readOut.setText (s + "%"); readOut.revalidate (); }}} клас ImageView разширява JScrollPane {частен панел JPanel = нов JPanel (); private Dimension originalSize = ново измерение (); частно изображение originalImage; частна икона ImageIcon; публичен ImageView (икона ImageIcon, модел BoundedRangeModel) {panel.setLayout (нов BorderLayout ()); panel.add (нов JLabel (икона)); this.icon = икона; this.originalImage = icon.getImage (); setViewportView (панел);model.addChangeListener (нов ModelListener ()); originalSize.width = icon.getIconWidth (); originalSize.height = icon.getIconHeight (); } клас ModelListener реализира 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 (); двоен множител = (двойна) стойност / (двойна) обхват; множител = множител == 0,0? 0,01: множител; Изображение мащабирано = originalImage.getScaledInstance ((int) (originalSize.width * множител), (int) (originalSize.height * множител), Image.SCALE_FAST); icon.setImage (мащабиран); panel.revalidate (); panel.repaint (); }}}}

Когато преместите плъзгача, плъзгачът променя стойността на своя модел. Тази промяна задейства известия за събития за двамата слушатели на промяна, регистрирани с модела, които коригират отчитането и мащабират изображението. И двамата слушатели използват събитието за промяна, предадено на

stateChanged()

за да се определи новата стойност на модела.

Swing е тежък потребител на шаблона на Observer - той реализира повече от 50 слушатели на събития за прилагане на специфично за приложението поведение, от реагиране на натиснат бутон до вето на събитие за затваряне на прозорец за вътрешна рамка. Но Swing не е единствената рамка, която използва шаблона на Observer добре - той се използва широко в Java 2 SDK; например: Абстрактният javax.namingнабор от инструменти за прозорци, рамката на JavaBeans, пакета и манипулаторите за въвеждане / извеждане.

Пример 1 по-конкретно показва използването на модела Observer с Swing. Преди да обсъдим повече подробности за шаблона на Observer, нека разгледаме как шаблонът обикновено се прилага.

Как работи моделът Observer

Фигура 2 показва как са свързани обектите в модела Observer.

Субектът, който е източник на събитие, поддържа колекция от наблюдатели и предоставя методи за добавяне и премахване на наблюдатели от тази колекция. Субектът също така прилага notify()метод, който уведомява всеки регистриран наблюдател за събития, които го интересуват. Субектите уведомяват наблюдателите, като се позовават на update()метода на наблюдателя .

Фигура 3 показва диаграма на последователността за модела Observer.

Обикновено някой несвързан обект ще извика метода на субекта, който променя състоянието на субекта. Когато това се случи, субектът се позовава на свой собствен notify()метод, който итерира над колекцията от наблюдатели, извиквайки update()метода на всеки наблюдател .

Моделът Observer е един от най-фундаменталните дизайнерски модели, тъй като позволява комуникацията на силно отделени обекти. В пример 1 единственото нещо, което моделът с ограничен диапазон знае за своите слушатели, е, че те прилагат stateChanged()метод. Слушателите се интересуват само от стойността на модела, а не от това как се прилага моделът. Моделът и неговите слушатели знаят много малко един за друг, но благодарение на модела на Observer те могат да общуват. Тази висока степен на разделяне между модели и слушатели ви позволява да изграждате софтуер, съставен от обекти, които могат да се включат, правейки вашия код изключително гъвкав и многократно използваем.

Java 2 SDK и модел на наблюдател

Java 2 SDK осигурява класическо изпълнение на модела Observer с Observerинтерфейса и Observableкласа от java.utilдиректорията. В Observableкласа представлява предмет; наблюдателите прилагат Observerинтерфейса. Интересното е, че тази класическа реализация на модел на наблюдател рядко се използва на практика, тъй като изисква субектите да разширят Observableкласа. Изискването на наследяване в този случай е лош дизайн, тъй като потенциално всеки тип обект е кандидат за предмет и защото Java не поддържа множествено наследяване; често тези предметни кандидати вече имат суперклас.

The event-based implementation of the Observer pattern, which was used in the preceding example, is the overwhelming choice for Observer pattern implementation because it doesn't require subjects to extend a particular class. Instead, subjects follow a convention that requires the following public listener registration methods:

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

Whenever a subject's bound property (a property that's been observed by listeners) changes, the subject iterates over its listeners and invokes the method defined by the XXXListener interface.

By now you should have a good grasp of the Observer pattern. The rest of this article focuses on some of the Observer pattern's finer points.

Anonymous inner classes

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

Използването на анонимни вътрешни класове, както е показано в предишния пример, беше много популярно сред разработчиците, така че започвайки с Java 2 Platform, Standard Edition (J2SE) 1.4, спецификацията JavaBeans пое отговорността за внедряването и създаването на тези вътрешни класове вместо вас с EventHandlerкласа, както е показано в пример 3:

Пример 3. Използване на java.beans.EventHandler

импортиране на java.beans.EventHandler; ... публичен клас Тест разширява JFrame {... публичен тест () {... model.addChangeListener (EventHandler.create (ChangeListener.class, this, "updateReadout")); } ... public void updateReadout () {String s = Integer.toString (model.getValue ()); readOut.setText (s + "%"); readOut.revalidate (); }} ...