Моделирование работы перекрестка на Java с помощью технологий объектно–ориентированного программирования
Предлагаемая статья посвящена разработке проекта, моделирующего работу перекрестка на Java. Предполагается смена цветов на светофорах перекрестка, а также движение машин по дорогам. В проекте использованы технологии объектно–ориентированного дизайна. Объектно–ориентированный дизайн (object – oriented design) есть искусство декомпозиции приложения в некоторое количество объектов – независимых компонент приложений, работающих вместе. Целью является разбить задачу на некоторое количество задач, которые проще решать и легче понимать.
Методология объектно–ориентированного дизайна есть система или множество правил, созданных, чтобы помочь идентифицировать объекты в приложении. Другими словами, методология объектно–ориентированного дизайна помогает разделить приложение на множество объектов, которые можно использовать повторно. Помимо того, что объектно–ориентированный дизайн есть наука, объектно–ориентированный дизайн есть также искусство. Никакая методология не является универсальной для любой жизненной ситуации. Важное значение имеет опыт.
Приведем несколько советов, которые помогут начинающим разработчикам программ (software designers):
- Рекомендовано, чтобы внутренность объекта была как можно менее доступной, в то время как его внешнее лицо (“public face”) было простым для понимания.
- Избегайте публичных переменных в Ваших объектах, исключение возможно только для констант. Вместо этого определяйте методы доступа (“accessor methods”) для установления и возвращения значений (даже если это простые типы). Далее, если Вы захотите, Вы сможете смодифицировать и расширить поведение Ваших объектов без разрушения других классов, которые зависят от них.
- Минимизируйте отношения между объектами и попытайтесь организовать связанные объекты в пакеты. Для того, чтобы усилить повторяемость использования Вашего кода, напишите Ваш код так, как Вы написали бы его для себя. Как известно, повторяемость использования Вашего кода является одной из основных характеристик зрелости созданного Вами программного продукта (Capability Maturity Model). Найдите, что один объект должен знать о других, чтобы выполнить свою работу, и минимизируйте отношения между ними.
Согласно теории объектно – ориентированного дизайна, работа над проектом состоит из четырех стадий:
- Анализ (analysis),
- Дизайн (design),
- Кодирование - написание программы (coding),
- Тестирование (testing).
В начале работы над проектом в стадиях анализа и дизайна были созданы диаграммы классов в Rational Rose. Rational Rose является средой для создания широкого спектра диаграмм для решения задач объектно–ориентированного дизайна. Rational Rose, наряду с UML и Cayenne, используется при разработке большими группами программистов сложных проектов. Разработка модели перекрестка является хорошим примером моделирования типичного приложения на UML средствами Rational Rose. Создание модели светофора показывает, как происходит моделирование типичного приложения на UML средствами Rose, и как по модели генерируется код Java.
Для работы в Rational Rose необходимо сделать некоторые подготовительные действия:
- Определить язык Java для построения модели. Для этого необходимо, прежде всего, проверить, что Java Add-In активен путем нажатия на Add-Ins > Add-In Manager. Если Java Add-In неактивен, то необходимо активировать его. Далее необходимо войти в Tools > Model Properties >. Затем в Notation выбрать Java из списка языков по умолчанию. Далее нажать на ОК для выбора языка Java.
- Java требует, чтобы переменная CLASSPATH была включена в путь к JDK API классам и другим необходимым библиотекам. Rose Java позволяет отображать пути к этим библиотекам, средам, приложениям, необходимым для нашей модели.
Задекларируем эти специфические Classpaths для нашей модели, используя Classpath на Rose Java Project Specification (Tools > Java/J2EE > Project Specification).
Далее происходит непосредственное создание модели.
В ходе работы над созданием модели перекрестка были построены следующие диаграммы классов:
Рис.1. Диаграмма классов в среде Rational Rose в проекте Перекресток
Следует пояснить некоторые детали диаграммы классов: каждый класс имеет атрибуты (переменные - attributes) и операции (методы – methods). В Rational Rose действует система обозначений для модификаторов, употребляемая в объектно–ориентированном программировании: private, public, protected. Атрибут или операция, декларированные как private, обозначаются ключом и прямоугольником, атрибут или операция, декларированные как protected, обозначаются замком и прямоугольником, атрибут или операция, декларированные как public, обозначаются прямоугольником.
Классы обозначаются прямоугольниками с тремя разделяющими линиями. В верхней части этого прямоугольника находится имя класса, в средней части – атрибуты, а нижней части – операции. В нашем проекте имеются классы Timer, Road, Light, Car. Классы связаны между собой отношениями наследственности, что в Rational Rose изображается стрелкой Generalize, направленной от подкласса (subclass) к суперклассу (superclass) .Пакеты обозначаются прямоугольником с расположенным слева вверху прямоугольником меньшего размера. В нашем проекте имеется пакет myroad. Классы Timer, Road, Light, Car принадлежат пакету myroad, что выражается отношением Realize, имеющим вид стрелки, направленной от пакета myroad к классам Timer, Road, Light, Car. В проекте задействован также интерфейс Runnable. Интерфейсы изображаются в Rational Rose в виде круга. Пакет myroad связан с интерфейсом Runnable отношением Realize, имеющим в случае отношений между пакетом и интерфейсом вид прямой линии.
Rose Java моделирует .java – файлы как компоненты (components). Созданный для нашей модели Component View моделирует физическую структуру наших файлов. Для успешного генерирования кода Rose требует, чтобы Java – классы в нашей модели были присвоены Java – компонентам в Component view нашей модели.
Далее в ходе работы над проектом был создан вид компонентов (Component View) и разработаны диаграммы компонентов (Component Diagrams). Эти диаграммы компонентов приведены на рис.2. На диаграммах компонентов компоненты изображаются в виде прямоугольников с двумя прямоугольниками меньшего размера, находящимися на левом ребре компонента. В нашем проекте имеются компоненты Timer, Road, Light, Car. Как уже было сказано, успешного генерирования кода Rose требует, чтобы Java – классы были присвоены Java – компонентам в Component view нашей модели. Поэтому присваиваем каждому компоненту соответствующий ему класс. Чтобы присвоить каждому компоненту соответствующий ему класс, необходимо совершить следующие действия:
Открыть стандартную спецификацию для компонента Open Standard Specification, например, Component Specification for Timer путем нажатия на правую клавишу мыши. Выбрать спецификацию Realizes. В списке классов нажать правой клавишей мыши на класс, который мы хотим присвоить компоненту и затем нажать на Assign. Нажать на ОК. Например, нажать правой клавишей мыши на класс Timer и затем нажать на Assign. Нажать на ОК. Таким образом, класс Timer присвоен компоненту Timer. Аналогичные действия производятся для классов Road, Light, Car. В проекте имеется также интерфейс Runnable. Связи между компонентами и интерфейсом выражаются отношением Dependency, графически изображаемым стрелкой с надписью implements. Связи между компонентами Timer, Road, Light, Car выражаются отношением Dependency, графически изображаемым стрелкой с надписью extends. Связи между пакетом myroad и компонентами Timer, Road, Light, Car выражается отношением Dependency, графически изображаемым стрелкой.
Рис.2. Component Diagrams/Component View
Опишем, как происходит генерирование кода. Код может генерироваться из диаграмм одной или более компонент. Необходимо выбрать одну или более компонент, затем нажать на Tools > Java/J2EE > Generate Java. Если генерирование кода происходит в первый раз, то появляется диалоговое окно, позволяющее отображать пакеты или компоненты в установленный нами Java classpath, то есть в заранее определенное место.
Если есть ошибки или замечания, то появится послание, и можно будет прочитать эти ошибки или замечания в Rose Log window , нажав View > Log. Когда генерация кода завершена, .java – файлы и связанные с ними структуры находятся на своем месте. Сгенерированные файлы также можно просмотреть из Rose.
После генерирования кода на Java можно просмотреть view (browse) сгенерированный код. Для этого необходимо нажать на Java/J2EE > Edit Code.
Rose снабжена внутренним редактором (Internal Editor) для редактирования и просмотра java – файлов. Сгенерированный файл может быть просмотрен внутри редактора.
Когда генерация кода завершена, необходимо подробно описать код.
Все классы в проекте объединяются в пакет myroad. Классом – отцом в данном проекте является класс Timer. Класс Timer имеет сыновей – класс Road, класс Light, класс Car. Остановимся на каждом из классов подробнее. В классе Timer происходит импортирование из пакетов java.awt.*; java.awt.event.*; java.applet.*:
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
Пакеты java.awt.*; java.awt.event.*; java.applet.* являются стандартными пакетами Java, обеспечивающие работу с GUI (Graphical User Interface) – графикой (java.awt.*; java.awt.event.* ) и с апплетами java.applet.*.
* в названии импортируемого пакета означает, что импортируются все классы данного пакета.
Класс Timer выполняет (implements) интерфейс Runnable. Интерфейс Runnable имеет методы run( ), start( ), stop( ). В проекте использовались потоки Thread. Интерфейс Runnable обеспечивает работу с потоками. Конструктор Timer( ) имеет методы run( ), start( ), stop( ). Метод run( ) содержит в себе оператор try/catch. Оператор try/catch работает с потоками. Оператор try работает с исключительной ситуацией, которая возникает в момент временной остановки потока на период времени period
Thread.sleep(period);
Поток остановлен sleep(спит) в течение периода времени period. Оператор catch перехватывает исключение е (Throwable e) , выброшенное в исключительной ситуации.
Метод start() также работает с потоком. Метод start() создает объект класса поток (Thread):
th= new Thread(this);
Метод stop() отвечает за остановку потока. Поток останавливается, и объекту th класса поток (Thread) присваивается значение null;
th.stop();
th=null;
Рассмотрим класс Road . Класс Road является сыном (subclass) класса Timer, что выражается следующим образом:
public class Road extends Timer implements Runnable{
Класс Road является сыном (subclass) класса Timer, что выражается глаголом extends (расширяет). Класс Timer является отцом (superclass) класса Road. Так же как и класс – отец Timer, класс Road выполняет (implements) интерфейс Runnable.
В методе init( ) класса Road происходит прорисовка картинки дороги. Для того, чтобы прорисовка была успешной, необходимо расположить GIF – файл с картинкой дороги road.GIF в директории d:\\road и использовать следующий оператор вызова
im=this.getToolkit().getImage("d:\\road\\road.GIF");
Для успешной работы проекта необходимо также создать массив crs[ ]. Создание массива и создание объекта класса происходит следующим образом:
crs=new Car[5];
crs[0]=new Car(0,this);
crs[1]=new Car(1,this);
crs[2]=new Car(2,this);
crs[3]=new Car(3,this);
crs[4]=new Car(4,this);
Массив crs[ ] состоит из пяти элементов. Размерность массива отражает максимальное количество машин, которые могут одновременно находиться на картинке. Далее необходимо создать массив светофоров, состоящий из восьми светофоров, и создать объект:
lts=new Light[8];
lts[0]=new Light(150,120,true,this);
lts[1]=new Light(320,230,true,this);
lts[2]=new Light(320,120,true,this);
lts[3]=new Light(150,230,true,this);
lts[4]=new Light(170,100,false,this);
lts[5]=new Light(295,250,false,this);
lts[6]=new Light(295,100,false,this);
lts[7]=new Light(170,250,false,this);
Следует заметить, что для создания элементов массива светофоров lts[] используется конструктор Light( ) класса Light. В методе paint ( ) происходит прорисовка дороги. Для этого используется графическая переменная gr типа Graphics.
Рассмотрим теперь класс Light. Класс Light является сыном (subclass) класса Timer, что выражается следующим образом:
public class Light extends Timer implements Runnable{
Глагол extends (расширять) означает, что класс Light является сыном (subclass) класса Timer [2]. Класс Timer является отцом (superclass) класса Light. Так же как и класс – отец Timer, класс Light выполняет (implements) интерфейс Runnable.
Конструктор Light( ) класса Light имеет методы open( ), paint ( ), run( ). Метод Light( ) имеет своими аргументами целые переменные mX, mY, обозначающие координаты светофора, логическую переменную type, регулирующую смену цветов в светофорах, переменную r типа Road. Метод open( ) устанавливает цвета светофора. Метод paint( ) определяет смену цветов светофора. Метод paint( ) имеет аргумент gr типа Graphics.
Метод paint( ) содержит условные операторы if / else и if, регулирующие смену цветов на всех светофорах, а также взаимодействие между различными светофорами.
if(col==true)gr.setColor(Color.green);
else {gr.setColor(Color.red);
if (yel==true)gr.setColor(Color.orange);}
gr.fillOval(x,y,10,10);
Прорисовка овала, означающего цвет светофора, происходит следующим образом:
gr.fillOval(x,y,10,10);
Метод run(), связанный с потоком, введенным в классе Timer – классе – отце класса Light, работает с потоками и отвечает за смену цветов светофора, которые фактически являются потоками. Также же как и в классе – отце Timer, оператор try/catch обрабатывает исключительную ситуацию: оператор try в исключительных случаях прерывает выполнение потока на период времени period
а оператор catch перехватывает исключение е и обрабатывает его.
try { Thread.sleep(period);}
catch(Throwable e){}
Класс Car так же, как и классы Road и Light, является сыном класса Timer. Класс Car выполняет (implements) интерфейс Runnable:
public class Car extends Timer implements Runnable{
Метод Car( ) является конструктором класса Car( ). Метод chng() отвечает за изменение координаты машины. Он использует математические функции Math.round и Math.random(). Метод init() прорисовывает четыре машины, движущиеся по две справа налево, слева направо, сверху вниз и снизу вверх:
im = rd.getToolkit().getImage("d:\\road\\cr1.GIF");
Для того, чтобы прорисовать машину, нужно поместить рисунок машины cr1.GIF с директорию d:\\road. Аналогично для других рисунков.
Иногда на картинке одновременно находятся пять машин. Это отражено в массиве crs[ ], имеющем пять элементов. Метод move() определяет координаты машин и прорисовывает их при помощи метода repaint( ) при движении справа налево и снизу наверх.
Метод paint( ) непосредственно рисует машины gr.drawImage(im,x,y,null);
Метод func() работает с массивом машин crs[ ]. Этот метод обеспечивает появление машин снизу, сверху, слева и справа после того, как предыдущие машины скрылись соответственно вверху, внизу, справа и слева.
Метод run() является одним из самых сложных методов класса Car. Метод run( ) обеспечивает взаимодействие между светофорами и машинами. Благодаря работе этого метода машины едут на красный сигнал светофора и стоят на зеленый сигнал светофора. Смена цветов обеспечивается потоками.
Возможен также поворот машины на перекрестке во время движения по дороге. Поворот обеспечивается методами chng() , run( ) и move( ).
После того, как проект проходит компиляцию, необходимо посмотреть файл Road.html. Именно там появляется картинка. Результатом работы проекта является визуализированная модель перекрестка, созданная на языке Java с применением объектно – ориентированных технологий.
Исходные файлы проекта см. в архиве.
Литература
- Niemeyer P., Peck J. Exploring Java. O’ Relly, Second edition, 2004, 602 p.
- Савитч У. Язык Java. Курс программирования. Пер.с англ. Изд.дом Вильямс, Москва, С. – Пб., Киев, 2002.