Основы объектно-ориентированного проектирования


Система управления лифтом


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

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

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

Данная реализация примера с лифтом, приспособленная к показу управления на экранах нескольких компьютеров через Интернет (а не для реальных лифтов), была использована на нескольких конференциях для демонстрации ОО-механизмов параллельности и распределенности.

Класс MOTOR описывает мотор, связанный с одной кабиной лифта и интерфейс с механическим оборудованием:

separate class MOTOR feature {ELEVATOR} move (floor: INTEGER) is -- Переместиться на этаж floor и сообщить об этом do "Приказать физическому устройству переместится на floor" signal_stopped (cabin) end signal_stopped (e: ELEVATOR) is -- Сообщить, что лифт остановился на этаже e do e.record_stop (position) end feature {NONE} cabin: ELEVATOR position: INTEGER is -- Текущий этаж do Result := "Текущий этаж, считанный с физических датчиков" end end

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

Причиной для выделения лифта и его мотора как сепаратных объектов является желание уменьшить "зернистость" запираний: сразу после того, как лифт пошлет запрос move своему мотору, он станет готов, благодаря политике ожидания по необходимости, к приему запросов от кнопок внутри и вне кабины. Он будет рассинхронизирован со своим мотором до получения вызова процедуры record_stop через процедуру signal_stopped. Экземпляр класса ELEVATOR будет только на очень короткое время зарезервирован вызовами от объектов классов MOTOR или BUTTON.

separate class ELEVATOR creation make feature {BUTTON} accept (floor: INTEGER) is -- Записать и обработать запрос на переход на floor do record (floor) if not moving then process_request end end feature {MOTOR} record_stop (floor: INTEGER) is -- Записать информацию об остановке лифта на этаже floor do moving := false; position := floor; process_request end feature {DISPATCHER} position: INTEGER moving: BOOLEAN feature {NONE} puller: MOTOR pending: QUEUE [INTEGER] -- Очередь ожидающих запросов -- (каждый идентифицируется номером нужного этажа) record (floor: INTEGER) is -- Записать запрос на переход на этаж floor do "Алгоритм вставки запроса на floor в очередь pending" end process_request is -- Обработка очередного запроса из pending, если такой есть local floor: INTEGER do if not pending.empty then floor := pending.item actual_process (puller, floor) pending.remove end end actual_process (m: separate MOTOR; floor: INTEGER) is -- Приказать m переместится на этаж floor do moving := True; m.move (floor) end endИмеются кнопки двух видов: кнопки на этажах, нажимаемые для вызова лифта на данный этаж, и кнопки внутри кабины, нажимаемые для перемещения лифта на соответствующий этаж. Эти два вида кнопок посылают разные запросы: запросы кнопок, расположенных внутри кабины, направляются этой кабине, а запросы кнопок на этажах могут обрабатываться любым лифтом, и поэтому они посылаются объекту-диспетчеру, опрашивающему разные лифты для выбора того, кто будет выполнять этот запрос. (Мы не приводим реализацию этого алгоритма выбора, поскольку это не существенно для данного рассмотрения, то же относится и к алгоритмам, используемым лифтами для управления их очередями запросов pending в классе ELEVATOR).



В классе FLOOR_BUTTON предполагается, что на каждом этаже имеется только одна кнопка. Нетрудно изменить этот проект так, чтобы поддерживались две кнопки: одна для запросов на движение вверх, а другая - вниз.

Удобно, хотя и не очень существенно, иметь общего родителя BUTTON для классов, представляющих оба вида кнопок. Напомним, что компоненты, экспортируемые ELEVATOR в BUTTON, также экспортируются в соответствии со стандартными правилами скрытия информации в оба потомка этого класса:

separate class BUTTON feature target: INTEGER end separate class CABIN_BUTTON inherit BUTTON feature cabin: ELEVATOR request is -- Послать своему лифту запрос на остановку на этаже target do actual_request (cabin) end actual_request (e: ELEVATOR) is -- Захватить e и послать запрос на остановку на этаже target do e.accept (target) end end separate class FLOOR_BUTTON inherit BUTTON feature controller: DISPATCHER request is -- Послать диспетчеру запрос на остановку на этаже target do actual_request (controller) end actual_request (d: DISPATCHER) is -- Послать d запрос на остановку на этаже target do d.accept (target) end endВопрос о включении и выключении света в кнопках здесь не рассматривается. Нетрудно добавить вызовы подпрограмм, которые будут этим заниматься.

Наконец, вот класс DISPATCHER. Чтобы разработать алгоритм выбора лифта в процедуре accept, потребуется разрешить ей доступ к атрибутам position и moving класса ELEVATOR, которые в полной системе должны быть дополнены булевским атрибутом going_up (движется_вверх). Такой доступ не вызовет никаких проблем, поскольку наш проект гарантирует, что объекты класса ELEVATOR никогда не резервируются на долгое время.

separate class DISPATCHER creation make feature {FLOOR_BUTTON} accept (floor: INTEGER) is -- Обработка запроса о посылке лифта на этаж floor local index: INTEGER; chosen: ELEVATOR do "Алгоритм определения лифта, выполняющего запрос для этажа floor" index := "Индекс выбранного лифта" chosen := elevators @ index send_request (chosen, floor) end feature {NONE} send_request (e: ELEVATOR; floor: INTEGER) is -- Послать лифту e запрос на перемещение на этаж floor do e.accept (floor) end elevators: ARRAY [ELEVATOR] feature {NONE} -- Создание make is -- Настройка массива лифтов do "Инициализировать массив лифтов" end end

Содержание раздела