3. Объектно-ориентированное программирование с явным выделением состояний
В предыдущей главе рассматривалась технология программирования, образованная сочетанием автоматного и процедурного подходов к разработке ПО. Надо сказать, что когда эта технология зарождалась, никто и не думал о сочетании подходов, оно возникло само собой. Теперь, спустя несколько лет, полезно разобраться, почему автоматы так хорошо вписались в процедурный подход и на каких ролях сочетаются в программировании с явным выделением состояний указанные парадигмы. Ведущую роль здесь сыграло понятие «автомат как подпрограмма». При таком понимании автоматов они органично вписываются в процесс проектирования «сверху вниз», в котором модульная структура системы строится из подпрограмм. Если автомат — это всего лишь частный случай подпрограммы, то никакого конфликта парадигм не возникает. Архитектура системы остается почти такой же, как и раньше, за исключением того, что некоторые подпрограммы в ней являются реализациями автоматов. Возможно ли так же органично объединить автоматный подход с какой-либо другой парадигмой программирования и получить от этого сочетания какие-либо преимущества? Вероятно, было бы интересно исследовать возможность использования автоматных концепций в контексте функционального или логического программирования, однако такое исследование, скорее всего, имело бы чисто теоретическую ценность. Если же речь идет о пользе для практического программирования, целесообразно рассмотреть совместное использование автоматного подхода и объектно-ориентированного программирования. Вот уже около двух десятилетий объектно-ориентированное программирование является наиболее широко используемым стилем разработки ПО в мире. Про этот стиль написано столько книг, а поддерживающие его языки настолько различны, что на данный момент среди разработчиков не существует однозначного понимания, что такое объектно-ориентированное программирование. Если попытаться обобщить все имеющиеся мнения на этот счет, получится нечто слишком абстрактное. Поэтому, по аналогии с предыдущей главой, прежде всего опишем вкратце, что авторы этой книги понимают под объектно-ориентированным программированием. Отметим, что мнение авторов нельзя считать наиболее распространенным, скорее, оно совпадает со взглядами, изложенными в книге Для подробного знакомства с объектно-ориентированным программированием авторы рекомендуют эту книгу. Объектно-ориентированная парадигма создания программного обеспечения (далее в качестве синонима используется термин объектная технология) характеризируется следующими основными концепциями. Объектная декомпозиция. Этот вид декомпозиции основан не на вопросе «Что делает система?» как декомпозиция сверху вниз, а на вопросе «Кто действует в системе?» Поскольку этот аспект является более устойчивым, то и построенная таким образом архитектура хорошо приспособлена к эволюции. В результате модули системы рождаются из сущностей, которые, однако, характеризуются не внутренним содержанием, а теми сервисами, которые они предоставляют. Такие сущности называются абстрактными типами данных (АТД). Формально абстрактный тип данных состоит из множества операций, снабженных описаниями их областей определения — предусловиями, и множества аксиом, которые задают свойства операций. Классы. Класс — это полная или частичная реализация АТД и единственный вид модуля в объектно-ориентированной системе. Такие модули взаимодействуют между собой через интерфейсы (с точностью до обозначений интерфейс соответствует понятию АТД, на котором основан класс). Интерфейс класса содержит сигнатуры его компонентов: запросов и команд, а также семантические свойства: предусловия и постусловия запросов и команд, инварианты класса. Компоненты класса соответствуют операциям АТД, предусловия компонентов — предусловиям операций, постусловия и инварианты — аксиомам АТД. Предусловия, постусловия и инварианты также называются утверждениями, в совокупности они образуют контракт класса или его компонента. Метод разработки программного обеспечения, в котором утверждения являются неотъемлемой частью текста программной системы, называется проектированием по контракту В соответствии с этим методом система считается корректной только в том случае, если перед вызовом любого компонента выполняется его предусловие, а по завершении работы компонента — его постусловие. Инвариант класса должен выполняться во всех устойчивых состояниях любого объекта этого класса (в тех состояниях, которые могут наблюдать клиенты класса).В отличие от модулей в структурной парадигме программирования (например, в языках Pascal и Ada), класс одновременно является модулем и типом. Любой тип в объектно-ориентированной программной системе основан на некотором классе. Объекты. Если класс полностью реализует АТД, то он может иметь экземпляры — объекты. Связь между объектом и классом имеет двоякую природу, как и сам класс. С одной стороны, класс — это тип объекта (и понятие типа здесь практически ничем не отличается от понятия типа переменной в необъектно-ориентированных языках), а с другой — класс как модуль определяет те операции, которые применимы к объекту. Объект — это единственный вид сущности, имеющийся в объектно-ориентированной программной системе во время выполнения. Вызов компонента на некотором объекте — основной вид операции в объектно-ориентированных вычислениях. Наследование — это механизм, позволяющий создавать повторно используемые программные модули, «не обладая бесконечной мудростью». С точки зрения класса как модуля, наследование — это механизм расширения, позволяющий определять новые классы, частично используя определения уже существующих. С помощью этого механизма возможно, во-первых, построить модифицированный класс без необходимости внесения изменений в существующий класс (следовательно, и в описания его клиентов), а во-вторых, вносить такие модификации, которые изначально не предполагались автором класса.С точки зрения класса как типа наследование — это механизм порождения подтипов. Подтипы, в свою очередь, обеспечивают один из видов полиморфизма, при котором к сущности некоторого типа, может быть во время выполнения, присоединен объект его подтипа. Полиморфизм позволяет клиентам одинаково работать с множеством «похожих» классов и поэтому является мощным механизмом повторного использования кода. Исследования, в которых вместе фигурируют автоматы и объектно-ориентированное программирование, ведутся различными авторами в течение длительного времени. Например, известно множество шаблонов для объектно-ориентированной реализации автоматов а диаграммы состояний стали частью объектно-ориентированного языка моделирования UML. Однако судя по тому, что автоматы на сегодняшний день редко используются при проектировании и реализации сложного поведения, результаты исследований нельзя назвать удовлетворительными. В работах, посвященных шаблонам (образцам) проектирования, зачастую утверждается, что концепция сложного поведения несовместима с объектной парадигмой. Поэтому существует очень много шаблонов объектно-ориентированной реализации автоматов, все они довольно сложные, и выбор подходящего для конкретной задачи представляет проблему Что касается языка UML, в нем существует три основных препятствия для использования диаграмм состояний как общепризнанного и распространенного средства описания сложного поведения. Во-первых, кроме диаграмм состояний для описания поведения предлагается использовать и другие типы диаграмм, и не существует общих правил, определяющих, когда и какие диаграммы следует применять. Во-вторых, в рамках унифицированного процесса разработки программ не было предложено подходов для совместного использования диаграмм, описывающих структурные и поведенческие свойства программ. И в-третьих, диаграммы для описания поведения в среде пользователей UML в основном используются как язык общения между разработчиками и для документирования программ, в то время как подобающая им роль — точная спецификация, которая может служить источником для ее программной интерпретации или генерации текста программы. Вместе с развитием процедурного программирования с явным выделением состояний по инициативе одного из авторов этой книги было положено начало развитию другой разновидности автоматного программирования, названной объектно-ориентированным программированием с явным выделением состояний. Это направление с самого начала было ориентировано на создание объектно-ориентированных программных систем со сложным поведением в целом, а не на описание конечных автоматов с помощью классов, как большинство образцов проектирования. Однако в вопросах технологии не все сразу оказалось так однозначно: это направление претерпело существенную эволюцию, которая не завершилась и до сих пор. Суть этой эволюции и ее нынешнее состояние будут отражены в данной главе.