Спроектируйте один и тот же небольшой контроллер светофора последовательно в процедурном стиле, в объектно-ориентированном, в функциональном и в логическом подходах; опишите ключевые различия в моделировании состояния, сопоставлении с реальными требованиями и в механизмах тестирования для каждого варианта
Краткое исходное требование (для всех четырёх проектов): один перекрёсток, один светофор с тремя состояниями {Green, Yellow, Red}, фиксированные длительности: зелёный G= 30 sG=\;30\ \text{s}G=30s, жёлтый Y= 5 sY=\;5\ \text{s}Y=5s, красный нужно так, чтобы не было одновременного зелёного в конфликтующих направлениях (в простейшей модели — красный = G+YG+YG+Y для противоположного направления). Далее — четыре подхода. 1) Процедурный стиль - Идея: глобальные переменные состояния и главный цикл, изменения состояния императивными инструкциями. - Псевдокод ``` enum State = {GREEN, YELLOW, RED} var state = GREEN var timer = 0 procedure init(): state = GREEN timer = 0 procedure tick(dt): timer = timer + dt if state == GREEN and timer >= GGG: state = YELLOW; timer = 0 else if state == YELLOW and timer >= YYY: state = RED; timer = 0 else if state == RED and timer >= G+YG + YG+Y: // возвращаемся на цикл сопредельного светофора state = GREEN; timer = 0 ``` - Моделирование состояния: одно глобальное состояние (переменная `state`) + счётчик времени. Изменения явные и последовательные. - Соответствие требованиям: легко выразить простые тайминги; сложные требований (пешеходы, приоритеты) ведут к росту условных операторов и побочных эффектов. - Тестирование: императивно — запускать `tick` с моделированием времени; легко писать интеграционные тесты (симуляция цикла). Модульные тесты сложнее, т.к. необходимо контролировать глобальное состояние; мокировать таймер вручную. 2) Объектно-ориентированный стиль - Идея: инкапсулировать состояние и поведение в классах; можно наследовать/компоновать. - Псевдокод ``` class TrafficLight: private State state private float timer constructor(): state = GREEN; timer = 0 method tick(dt): timer += dt switch state: case GREEN: if timer >= GGG then state = YELLOW; timer = 0 case YELLOW: if timer >= YYY then state = RED; timer = 0 case RED: if timer >= G+YG + YG+Y then state = GREEN; timer = 0 method getState(): return state method setTimings(newG, newY): ... // конфигурация ``` - Моделирование состояния: объект хранит внутреннее состояние и инварианты; можно создавать экземпляры для каждого направления, добавлять методики синхронизации между объектами. - Соответствие требованиям: хорош для расширения (добавить пешеходный свет как отдельный объект, стратегии переключения, интерфейсы для конфигурации). Инкапсуляция помогает реализовать ограничения и валидацию. - Тестирование: юнит-тесты методов объекта (мокирование времени или передача фиктивного таймера), тесты интерфейсов, можно использовать заглушки/фейковые объекты для внешних зависимостей. 3) Функциональный стиль - Идея: чистые функции без побочных эффектов; состояние — неизменяемая структура, функции принимают состояние и событие/время и возвращают новое состояние. - Псевдокод (функциональный стиль) ``` type State = { mode : GREEN|YELLOW|RED, timer : Float } let init = { mode = GREEN, timer = 0.0 } let step (s:State) (dt:Float) : State = let t = s.timer + dt match s.mode with | GREEN -> if t >= GGG then { mode = YELLOW; timer = 0.0 } else { s with timer = t } | YELLOW -> if t >= YYY then { mode = RED; timer = 0.0 } else { s with timer = t } | RED -> if t >= G+YG + YG+Y then { mode = GREEN; timer = 0.0 } else { s with timer = t } ``` - Моделирование состояния: состояние — неизменяемое значение; переходы — функции state×dt→state′state \times dt \to state'state×dt→state′. Это облегчает формальное рассуждение и референциальную прозрачность. - Соответствие требованиям: удобно выражать правила и композицию (комбинировать контроллеры, добавлять детекторы событий); сложное управление побочными эффектами (взаимодействие с аппаратурой) оформляется через эффекты (монады/очереди событий). - Тестирование: очень просто юнит-тестировать функции — входное состояние + dt детерминированно дают выход; легко генерировать набор сценариев (property-based testing), формально проверять инварианты. 4) Логический (реляционно-правилный) подход - Идея: описать факты и правила перехода как логические отношения; система выводит допустимые последовательности/проверяет свойства. - Пример на стиле Prolog-псевдо: ``` duration(green, GGG). duration(yellow, YYY). duration(red, G+YG + YG+Y). next(green, yellow) :- true. next(yellow, red) :- true. next(red, green) :- true. transition(State, Time, NextState, Remain) :- duration(State, D), Time >= D, next(State, NextState), Remain is Time - D. // Запрос: будет ли через T сек светофор в состоянии S? // ?- transition(green, 40, S, R). ``` - Моделирование состояния: состояние — набор фактов (ситуаций); переходы — логические правила; можно естественно выражать свойства безопасности (например, "не бывает green в обеих направлениях") как ограничения/запреты. - Соответствие требованиям: хорошо для верификации требований, поиска контрпримеров и планирования (например, найти последовательность событий, ведущую к опасному состоянию). Сложности при реализации реального контроллера в реальном времени (интерфейс с железом) — требует мостов к императивному коду. - Тестирование/валидация: проверка свойств через поиск (модель-проверка), извлечение контрпримеров, автоматическое доказательство инвариантов. Ключевые различия (кратко) - Моделирование состояния: - Процедурный: глобальные изменяемые переменные; явные побочные эффекты. - OOP: состояние инкапсулировано в объектах; поведение в методах. - Функциональный: состояние — неизменяемая структура; переходы — чистые функции. - Логический: состояние — логические факты; переходы — правила/отношения. - Сопоставление с реальными требованиями: - Процедурный: прост в прототипировании, но плохо масштабируется для сложных требований и конкурирующих требований. - OOP: хорош для больших систем с расширяемостью и инкапсуляцией (интерфейсы для аппаратуры, стратегии). - Функциональный: облегчает формальную проверку логики переключений и тестирование; требует архитектуры для побочных эффектов. - Логический: лучшe для спецификации и верификации требований, поиска контрпримеров; не прямой путь к исполнению на встраиваемом контроллере. - Механизмы тестирования: - Процедурный: интеграционные сценарии и манипуляция глобальным временем; сложнее модульно. - OOP: юнит-тесты методов, мокирование внешних зависимостей; тесты инвариантов состояния объекта. - Функциональный: простые чистые юнит-тесты, property-based тестирование, легко воспроизводимые сценарии. - Логический: проверка свойств через запросы и поиск контрпримеров (модель-проверка), доказательство инвариантов. Рекомендация по выбору - Для простого встраиваемого контроллера — процедурный или OOP (в зависимости от языка/оборудования). - Для сложной логики и верификации требований — функциональный или логический (функциональный для чистой реализации и тестирования, логический для спецификации и формальной верификации). Если нужно, могу дать конкретную реализацию под выбранный язык (C, Python, Haskell, Prolog) с тестами.
1) Процедурный стиль
- Идея: глобальные переменные состояния и главный цикл, изменения состояния императивными инструкциями.
- Псевдокод
```
enum State = {GREEN, YELLOW, RED}
var state = GREEN
var timer = 0
procedure init():
state = GREEN
timer = 0
procedure tick(dt):
timer = timer + dt
if state == GREEN and timer >= GGG:
state = YELLOW; timer = 0
else if state == YELLOW and timer >= YYY:
state = RED; timer = 0
else if state == RED and timer >= G+YG + YG+Y: // возвращаемся на цикл сопредельного светофора
state = GREEN; timer = 0
```
- Моделирование состояния: одно глобальное состояние (переменная `state`) + счётчик времени. Изменения явные и последовательные.
- Соответствие требованиям: легко выразить простые тайминги; сложные требований (пешеходы, приоритеты) ведут к росту условных операторов и побочных эффектов.
- Тестирование: императивно — запускать `tick` с моделированием времени; легко писать интеграционные тесты (симуляция цикла). Модульные тесты сложнее, т.к. необходимо контролировать глобальное состояние; мокировать таймер вручную.
2) Объектно-ориентированный стиль
- Идея: инкапсулировать состояние и поведение в классах; можно наследовать/компоновать.
- Псевдокод
```
class TrafficLight:
private State state
private float timer
constructor():
state = GREEN; timer = 0
method tick(dt):
timer += dt
switch state:
case GREEN: if timer >= GGG then state = YELLOW; timer = 0
case YELLOW: if timer >= YYY then state = RED; timer = 0
case RED: if timer >= G+YG + YG+Y then state = GREEN; timer = 0
method getState(): return state
method setTimings(newG, newY): ... // конфигурация
```
- Моделирование состояния: объект хранит внутреннее состояние и инварианты; можно создавать экземпляры для каждого направления, добавлять методики синхронизации между объектами.
- Соответствие требованиям: хорош для расширения (добавить пешеходный свет как отдельный объект, стратегии переключения, интерфейсы для конфигурации). Инкапсуляция помогает реализовать ограничения и валидацию.
- Тестирование: юнит-тесты методов объекта (мокирование времени или передача фиктивного таймера), тесты интерфейсов, можно использовать заглушки/фейковые объекты для внешних зависимостей.
3) Функциональный стиль
- Идея: чистые функции без побочных эффектов; состояние — неизменяемая структура, функции принимают состояние и событие/время и возвращают новое состояние.
- Псевдокод (функциональный стиль)
```
type State = { mode : GREEN|YELLOW|RED, timer : Float }
let init = { mode = GREEN, timer = 0.0 }
let step (s:State) (dt:Float) : State =
let t = s.timer + dt
match s.mode with
| GREEN -> if t >= GGG then { mode = YELLOW; timer = 0.0 } else { s with timer = t }
| YELLOW -> if t >= YYY then { mode = RED; timer = 0.0 } else { s with timer = t }
| RED -> if t >= G+YG + YG+Y then { mode = GREEN; timer = 0.0 } else { s with timer = t }
```
- Моделирование состояния: состояние — неизменяемое значение; переходы — функции state×dt→state′state \times dt \to state'state×dt→state′. Это облегчает формальное рассуждение и референциальную прозрачность.
- Соответствие требованиям: удобно выражать правила и композицию (комбинировать контроллеры, добавлять детекторы событий); сложное управление побочными эффектами (взаимодействие с аппаратурой) оформляется через эффекты (монады/очереди событий).
- Тестирование: очень просто юнит-тестировать функции — входное состояние + dt детерминированно дают выход; легко генерировать набор сценариев (property-based testing), формально проверять инварианты.
4) Логический (реляционно-правилный) подход
- Идея: описать факты и правила перехода как логические отношения; система выводит допустимые последовательности/проверяет свойства.
- Пример на стиле Prolog-псевдо:
```
duration(green, GGG).
duration(yellow, YYY).
duration(red, G+YG + YG+Y).
next(green, yellow) :- true.
next(yellow, red) :- true.
next(red, green) :- true.
transition(State, Time, NextState, Remain) :-
duration(State, D),
Time >= D,
next(State, NextState),
Remain is Time - D.
// Запрос: будет ли через T сек светофор в состоянии S?
// ?- transition(green, 40, S, R).
```
- Моделирование состояния: состояние — набор фактов (ситуаций); переходы — логические правила; можно естественно выражать свойства безопасности (например, "не бывает green в обеих направлениях") как ограничения/запреты.
- Соответствие требованиям: хорошо для верификации требований, поиска контрпримеров и планирования (например, найти последовательность событий, ведущую к опасному состоянию). Сложности при реализации реального контроллера в реальном времени (интерфейс с железом) — требует мостов к императивному коду.
- Тестирование/валидация: проверка свойств через поиск (модель-проверка), извлечение контрпримеров, автоматическое доказательство инвариантов.
Ключевые различия (кратко)
- Моделирование состояния:
- Процедурный: глобальные изменяемые переменные; явные побочные эффекты.
- OOP: состояние инкапсулировано в объектах; поведение в методах.
- Функциональный: состояние — неизменяемая структура; переходы — чистые функции.
- Логический: состояние — логические факты; переходы — правила/отношения.
- Сопоставление с реальными требованиями:
- Процедурный: прост в прототипировании, но плохо масштабируется для сложных требований и конкурирующих требований.
- OOP: хорош для больших систем с расширяемостью и инкапсуляцией (интерфейсы для аппаратуры, стратегии).
- Функциональный: облегчает формальную проверку логики переключений и тестирование; требует архитектуры для побочных эффектов.
- Логический: лучшe для спецификации и верификации требований, поиска контрпримеров; не прямой путь к исполнению на встраиваемом контроллере.
- Механизмы тестирования:
- Процедурный: интеграционные сценарии и манипуляция глобальным временем; сложнее модульно.
- OOP: юнит-тесты методов, мокирование внешних зависимостей; тесты инвариантов состояния объекта.
- Функциональный: простые чистые юнит-тесты, property-based тестирование, легко воспроизводимые сценарии.
- Логический: проверка свойств через запросы и поиск контрпримеров (модель-проверка), доказательство инвариантов.
Рекомендация по выбору
- Для простого встраиваемого контроллера — процедурный или OOP (в зависимости от языка/оборудования).
- Для сложной логики и верификации требований — функциональный или логический (функциональный для чистой реализации и тестирования, логический для спецификации и формальной верификации).
Если нужно, могу дать конкретную реализацию под выбранный язык (C, Python, Haskell, Prolog) с тестами.