Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Введение в паттерны проектирования Java.doc
Скачиваний:
0
Добавлен:
01.03.2025
Размер:
170.5 Кб
Скачать

Паттерн представление-модель-контроллер(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);

}

}