Студопедия — Паттерн наблюдатель
Студопедия Главная Случайная страница Обратная связь

Разделы: Автомобили Астрономия Биология География Дом и сад Другие языки Другое Информатика История Культура Литература Логика Математика Медицина Металлургия Механика Образование Охрана труда Педагогика Политика Право Психология Религия Риторика Социология Спорт Строительство Технология Туризм Физика Философия Финансы Химия Черчение Экология Экономика Электроника

Паттерн наблюдатель






Вы знакомы с понятием подписка. Сколько людей мучаются от бесконечных SMS, посылаемых различными магазинами, банками и т.д. о проходящих у них акциях, распродажах, кредитах. Эта ситуация описывается паттерном наблюдатель.

У нас есть наблюдаемый объект – магазин и множество наблюдателей за этим магазином – владельцы мобильных телефонов. Как только в магазине возникает какое-то неординарное событие (распродажа, акция, скидки), владельцы телефонов получают сообщение об этом событии.

Создадим два интерфейса Observer и Observable. Метод update интерфейса Observer предназначен для передачи сообщения клиентам. Методы registerObserver и removeObserver интерфейса Observable предназначены для регистрации и удаления клиентов магазина. Метод notifyObservers служит для уведомления клиентов о различных событиях в магазине.

package observer;

public interface Observer {

public void update (String sms);

}

package observer;

public interface Observable {

public void registerObserver(Observer o);

public void removeObserver(Observer o);

public void notifyObservers();

}

Класс Shop (магазин) наследует интерфейс Observable(доступный для наблюдения) и содержит список всех клиентов магазина (observers), дату очередной акции магазина (date) и посылаемое клиенту сообщение (message). Метод sendMessage создает сообщение, приписывая к сообщению текущую дату. При каждом изменении переменной message вызывается метод notifyObservers, который рассылает сообщение всем подписчикам, т.е. клиентам, зарегистрированным методом registerObserver.

package observer;

import java.util.ArrayList;

import java.util.Date;

public class Shop implements Observable{

Date date;

String message;

private ArrayList<Observer> observers = new ArrayList<Observer>();

void setDate(){

date = new Date();

}

String sendMessage(){

setDate();

return message+date;

}

void setMessage(String sms){

message= sms;

notifyObservers();

}

public void registerObserver(Observer o) {

observers.add(o);

}

public void removeObserver(Observer o) {

int i = observers.indexOf(o);

if (i>=0) observers.remove(i);

}

public void notifyObservers() {

for (Observer observer: observers){

observer.update(sendMessage());

}

}

}

 

Класс клиент Client наследует интерфейс Observer и содержит список всех сообщений, полученных клиентом (sms). В методе update очередное сообщение добавляется в список полученных sms. В методе disply выводятся все сообщения присланные клиенту.

package observer;

 

import java.util.ArrayList;

 

public class Client implements Observer {

ArrayList<String> sms;

String name;

Client(String name){

sms=new ArrayList<String>();

this.name = name;

}

public void update(String str) {

sms.add(str);

}

void disply(){

for(String x:sms)

System.out.println("Client "+name+" receive the message "+x);

}

}

Теперь необходимо создать магазин и его клиентов. Каждый клиент регистрируется в магазине - magazine.registerObserver(client[i]). Затем всем клиентам рассылаются сообщения с разной датой magazine.setMessage("Sale "). Демонстрируем все полученные клиентами сообщения

for(int i=0;i<4;i++){

client[i].disply();

}

 

package observer;

public static void main(String[] args) throws InterruptedException {

Shop magazine = new Shop();

Client [] client = new Client[4];

for(int i=0;i<4;i++){

client[i] = new Client("Cl "+i);

magazine.registerObserver(client[i]);

}

for(int i=0;i<5;i++){

Thread.sleep(1000);

magazine.setMessage("Sale ");

}

for(int i=0;i<4;i++){

client[i].disply();

}

}

}

В java.util имеется реализация наблюдателя и наблюдаемого объекта – интерфейс java.util.observer и класс java.util.observable. Интерфейс содержит метод update, а наблюдаемый объект может добавлять /удалять наблюдателей методами addObserver/deleteObserver. Также наблюдаемый объект сообщает наблюдателям о совершенных изменениях методом notifyObservers. Перед вызовом этого метода необходимо сообщить о том, что наблюдаемые объекты изменились - setChanged. Метод setChanged полезен в некоторых случаях. Например, изменение наблюдаемых параметров может происходить очень часто - раз в 10 милисекунд, а отражать это изменение нужно раз в секунду, тогда вызовом метода setChanged эта проблема решается.

Рассмотрим простой пример реализации наблюдателя с использованием java.util.

Пусть у нас имеется наблюдаемый объект TestObservable. Этот объект содержит только од ну переменную – строку name. Метод modify утверждает, что объект TestObservable изменился. Понятно, что данный объект изменяется только при создании.

public class TestObservable extends java.util.Observable {

private String name = "";

public TestObservable(String name) {

this.name = name;

}

 

public void modify() {

setChanged();

}

 

public String getName() {

return name;

}

}

Наблюдатель – экземпляр класса TestObserver, наследующий интерфейс java.util.Observable, перегружает метод update. В методе update используется объект типа Observable и объект типа Object. Вызвавший update объект «о» предоставляет метод getName для доступа к наблюдаемой строке name. Также в функции update доступен объект, переданный в качестве параметра функции notifyObservers объекта типа Observable. В данном случае таким параметром является строка.

public class TestObserver implements java.util.Observer {

private String name = "";

 

public TestObserver(String name) {

this.name = name;

}

public void update(java.util.Observable o,Object arg) {

String str = "Called update of " + name;

str += " from " + ((TestObservable)o).getName();

str += " with argument " + (String)arg;

System.out.println(str);

}

}

Рассмотрим реализацию паттерна наблюдатель. В функции main создаются объекты:

to – наблюдаемый объект,

о1 - наблюдатель Observer 1,

о2 - наблюдатель Observer 2.

Вызов метода addObserver для объекта to to.addObserver(o1) и to.addObserver(o2) назначает наблюдателей объекта to. Вызов метода modify, устанавливает состояние – «наблюдаемый объект был изменен». После этого notifyObservers сообщает наблюдателям об изменении наблюдаемого объекта.

 

public class Test {

public Test() {

}

public static void main(String[] args) {

Test test = new Test();

TestObservable to = new TestObservable("Observable");

TestObserver o1 = new TestObserver("Observer 1");

TestObserver o2 = new TestObserver("Observer 2");

to.addObserver(o1);

to.addObserver(o2);

to.modify();

to.notifyObservers("Notify argument");

}

}

В качестве упражнения напишите пример с рассылкой сообщений с использованием java.util.observer и класс java.util.observable.

Паттерн представление-модель-контроллер(Model-view-controller MVC)

MVC относится к составным паттернам. В этом паттерне выделяют:

  • Модель(model)- средство хранения данных приложения,
  • Представление(view) – визуальное представление данных модели или пользовательский интерфейс.
  • Контроллер (controller)- связывает модель и представление.

Данная схема проектирования часто используется для построения каркаса приложения.

Применение MVC может выполнить следующие задачи:

1. К одной модели можно присоединить несколько представлений, не затрагивая при этом реализацию модели. Например, некоторые данные могут быть одновременно представлены в виде электронной таблицы, гистограммы и круговой диаграммы.

2. Не изменяя реализацию представлений, можно поменять реакции на действия пользователя (нажатие мышью на кнопке, ввод данных), для этого достаточно использовать другой контроллер.

3. Ряд разработчиков специализируется только в одной из областей: либо разрабатывают графический интерфейс, либо разрабатывают бизнес-логику. Поэтому возможно добиться того, что программисты, занимающиеся разработкой модели, вообще не будут осведомлены о том, какое представление будет использоваться.

4. Наиболее типичная реализация отделяет представление от модели путем установления между ними протокола взаимодействия, используя аппарат событий (подписка/оповещение). При каждом изменении внутренних данных модель оповещает все зависящие от неё представления. Для этого используется паттерн «наблюдатель». При обработке реакции пользователя представление выбирает, в зависимости от нужной реакции, нужный контроллер, который обеспечит ту или иную связь с моделью. Для этого используется шаблон «стратегия», или вместо этого может быть модификация с использованием шаблона «команда» А для возможности однотипного обращения с подобъектами сложно-составного иерархического вида может использоваться шаблон «компоновщик».

 

Применим схему MVC для создания графического редактора.

  • Моделью данных в нашем приложении будет некоторое хранилище фигур с набором функций для работы с этими фигурами.
  • Представление данных – это панель для рисования.
  • Контроллер – это некоторый класс, связывающий панель и модель данных.

Создадим модель данных, которые будут храниться в графическом редакторе. Во второй главе был создан проект редактора, в котором пользователь может нарисовать одну фигуру – закрашенную или незакрашенную, прямоугольник или овал. В этом проекте не было разделения на представление и модель. Панель, на которой рисовалась фигура, являлась одновременно моделью и представлением данных. Данными являлась рисуемая фигура(shape).

Пусть в модели все рисуемые фигуры будут храниться в ArrayList<MyShape> list. Фигура, которая рисуется в данный момент - activeShape. Для добавления activeShape в коллекцию фигур служит метод add, для создания activeShape – метод setNewActiveShape. При создании новой фигуры используется порождающий паттерн «прототип», который будет рассмотрен в следующей главе. При изменении размеров рисуемой фигуры (activeShape) работает метод setShapeSize, который устанавливает размер фигуры в соответствие с координатами, хранящимися в массиве точек. Метод draw рисует все фигуры, которые хранятся в list. Для реализации функции «рисование» данная модель обладает всеми необходимыми функциями.

 

public class Model extends Observable{

ArrayList<MyShape> list;

private MyShape activeShape;

Model(){

list = new ArrayList<MyShape>();

}

void add(){

list.add(activeShape);

}

void setShapeSize(Point2D[]p){

activeShape.setShapeSize(p);

notifyPanel();

}

void setActiveShape(MyShape s){

activeShape = s;

}

void setNewActiveShape(){

activeShape = activeShape.clone();

}

void draw(Graphics g){

Graphics2D g2 = (Graphics2D)g;

for(MyShape x:list)x.draw(g2);

}

void notifyPanel(){

setChanged();

notifyObservers();

}

}

Заметим, что модель наследует класс Observable и является наблюдаемым объектом. В функции notifyPanel вызываются методы setChanged, который устанавливает состояние происшедших изменений, и notifyObservers, который оповещает подписчиков модели об изменениях. Метод notifyPanel вызывается в функции setShapeSize после изменения размеров рисуемой фигуры. Таким образом, панель оповещается о том, что надо перерисовать экран и отобразить новые размеры фигуры.

Создадим представление - панель, которая будет отображать информацию из описанной выше модели. В функции панели будет входить обработка событий мыши и передача «мышиных» координат соответствующим методам контроллера. Также панель будет перерисовывать экран и реализовывать метод update.

 

public class MyPanel extends JPanel implements Observer{

Controller controller;

//конктруктор

MyPanel(Controller contr){

controller = contr;

this.addMouseListener(new MouseAdapter() {

@Override

public void mousePressed(MouseEvent arg0) {

controller.executePress(arg0.getPoint());

}

});

addMouseMotionListener(new MouseMotionAdapter() {

@Override

public void mouseDragged(MouseEvent arg0) {

controller.executeDrag(arg0.getPoint());

}

});

}

//конец конструктора

@Override

public void paintComponent(Graphics g){

super.paintComponent(g);

controller.draw(g);

}

public void update(Observable arg0, Object arg1) {

repaint();

}

}

Как видим представление(MyPanel) получилось достаточно простое. Вся функциональность должна выполняться в модели и контроллере. MyPanel наследует интерфейс Observer и является наблюдателем. При изменении наблюдаемого объекта вызывается функция update, которая перерисовывает экран с помощью repaint.

Рассмотрим контроллер. При создании контроллера(Controller) использовался паттерн singleton, который позволяет создавать в проекте только один экземпляр данного класса. Этот паттерн мы рассмотрим позже. Класс Controller имеет доступ к модели, хранит массив точек p, необходимых для реализации действий с фигурами, хранит «текущую фигуру» shape, т.е. характеристики фигуры, которая будет рисоваться. Для формирования свойств shape служат методы setRectangularShape и setColorBehavior.

 

public class Controller {

private Model model;

private Activity action;

private MyShape shape;

private static Controller controller;

private Controller(Model m){

model=m;

shape=new MyShape();

}

void setActivity(Activity act){

action = act;

}

void setMyShape(MyShape s){

shape= s;

model.setActiveShape(shape);

}

void setRectangularShape(RectangularShape r){

shape.setShape(r);

model.setActiveShape(shape);

}

void setColorBehavior(ColorBehavior b){

shape.setColorBehavior(b);

model.setActiveShape(shape);

}

void executePress(Point2D point){

action.executePress(model, point);

}

void executeDrag(Point2D point){

action.executeDrag(model, point);

}

void draw(Graphics g){

model.draw(g);

}

public static Controller getInstance(Model model){

if(controller ==null)

controller = new Controller(model);

return controller;

}

}

 

Для выполнения действий над фигурами служит переменная action. При реализации различных действий над фигурами использовался паттерн стратегия, описанный в 1 главе. В нашей реализации редактора надо различать два действия над фигурами – рисовать фигуру или перемещать фигуру. Обрабатываются два события мыши – mousePressed в методе executePress и mouseDragged в методе executeDrag. При рисовании фигуры в методе executePress необходимо добавить новую фигуру в модель и в executeDrag изменять её координаты. При перемещении фигуры в методе executePress надо найти в модели фигуру, содержащую координаты мыши и в executeDrag перемещать эту фигуру. В зависимости от типа переменной action эти действия будут реализованы. Как этого достичь?

Создадим интерфейс Activity.

public interface Activity {

void setPoint(Point2D[]p);

void executePress(Model model,Point2D p);

void executeDrag(Model model,Point2D p);

 

}

Пусть этот интерфейс наследует класс DrawAction, отвечающий за функцию рисования. В методе setPoint устанавливается ссылка на массив точек (созданный в контроллере). В методе executePress в нулевом элементе массива запоминается точка «mousePressed» и вызываются методы модели, которые создают новую фигуру с характеристиками activeShape model.setNewActiveShape (activeShape передана в модель контроллером ранее в методе setMyShape). В методе executeDrag в первом элементе массива точек запоминается вторая координата мыши (mouseDragged) и вызывается model.setShapeSize, который перерисовывает фигуру с новыми границами.

public class DrawAction implements Activity{

Point2D [] p;

MyShape shape;

DrawAction(){

p=new Point2D[2];

}

public void setPoint(Point2D[]p) {

this.p = p;

}

public void executePress(Model model,Point2D point) {

p[0]=point;

model.setNewActiveShape();

model.add();

 

}

public void executeDrag(Model model, Point2D point) {

p[1]=point;

model.setShapeSize(p);

}

}

В качестве упражнения напишите класс MoveAction, который будет отвечать за передвижение фигур. В класс Model необходимо добавить:

· метод findShape(Point2D p) для поиска фигуры, содержащей точку р. Для этого в классе MyShape для объекта shape нужно вызвать метод contains(p), который возвращает true, если фигура содержит точу;

· метод moveShape(Point2D[]p), который будет менять координаты у фигуры, выбранной в методе findShape. Ниже приведен код функции, чтобы не затруднять читателя расчетами новых координат.

void moveShape(Point2D[]p){

double deltaX = p[0].getX()-p[1].getX();

double deltaY = p[0].getY()-p[1].getY();

if(movedShape!=null){

double xMin = movedShape.getMinX()-deltaX;

double yMin = movedShape.getMinY()-deltaY;

double xMax = movedShape.getMaxX()-deltaX;

double yMax = movedShape.getMaxY()-deltaY;

movedShape.setShapeSize(xMin, yMin, xMax, yMax);

p[0]=p[1];

notifyPanel();

}

}

Для соединения выше перечисленных классов необходим еще один класс – компоновщик, который реализует паттерн «компоновщик». Пусть таким классом будет класс MyFrame, который в дальнейшем будет реализовывать меню для выбора действий и фигур. В этом классе создаются – модель, контроллер, панель и фигура. Методом model.addObserver(panel) панель назначается наблюдателем над моделью. В строке controller.setMyShape(new MyShape(new Rectangle2D.Double(),new ColorShape(Color.GREEN))) назначается рисуемая фигура. В строке controller.setActivity(new DrawAction()) назначается действие. В результате может быть нарисовано множество различных зеленых прямоугольников.

 

public class MyFrame extends JFrame{

Model model;

MyPanel panel;

Controller controller;

MyFrame(){

model = new Model();

shape = new MyShape(new Rectangle2D.Double(),new ColorShape(Color.GREEN));

//singleton

controller = Controller.getInstance(model);

controller.setMyShape(shape);

controller.setActivity(new DrawAction());

panel = new MyPanel(controller);

model.addObserver(panel);

add(panel);

setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

setSize(300, 300);

setVisible(true);

}

}







Дата добавления: 2015-09-15; просмотров: 415. Нарушение авторских прав; Мы поможем в написании вашей работы!



Кардиналистский и ординалистский подходы Кардиналистский (количественный подход) к анализу полезности основан на представлении о возможности измерения различных благ в условных единицах полезности...

Обзор компонентов Multisim Компоненты – это основа любой схемы, это все элементы, из которых она состоит. Multisim оперирует с двумя категориями...

Композиция из абстрактных геометрических фигур Данная композиция состоит из линий, штриховки, абстрактных геометрических форм...

Важнейшие способы обработки и анализа рядов динамики Не во всех случаях эмпирические данные рядов динамики позволяют определить тенденцию изменения явления во времени...

Принципы и методы управления в таможенных органах Под принципами управления понимаются идеи, правила, основные положения и нормы поведения, которыми руководствуются общие, частные и организационно-технологические принципы...

ПРОФЕССИОНАЛЬНОЕ САМОВОСПИТАНИЕ И САМООБРАЗОВАНИЕ ПЕДАГОГА Воспитывать сегодня подрастающее поколение на со­временном уровне требований общества нельзя без по­стоянного обновления и обогащения своего профессио­нального педагогического потенциала...

Эффективность управления. Общие понятия о сущности и критериях эффективности. Эффективность управления – это экономическая категория, отражающая вклад управленческой деятельности в конечный результат работы организации...

Условия, необходимые для появления жизни История жизни и история Земли неотделимы друг от друга, так как именно в процессах развития нашей планеты как космического тела закладывались определенные физические и химические условия, необходимые для появления и развития жизни...

Метод архитекторов Этот метод является наиболее часто используемым и может применяться в трех модификациях: способ с двумя точками схода, способ с одной точкой схода, способ вертикальной плоскости и опущенного плана...

Примеры задач для самостоятельного решения. 1.Спрос и предложение на обеды в студенческой столовой описываются уравнениями: QD = 2400 – 100P; QS = 1000 + 250P   1.Спрос и предложение на обеды в студенческой столовой описываются уравнениями: QD = 2400 – 100P; QS = 1000 + 250P...

Studopedia.info - Студопедия - 2014-2024 год . (0.012 сек.) русская версия | украинская версия