Паттерн Абстрактная фабрика на C++

Паттерн Абстрактная фабрика на C++

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

Абстрактная фабрика (Abstract factroy) является одним из самых известных представителей порождающих паттернов. Классы-фабрики, реализующие этот паттерн, решают единственную задачу — создание взаимосвязанных объектов.

Основная идея Абстрактной фабрики заключается в том, что мы избавляемся от зависимостей при создании конкретных экземпляров классов. Например, в функции generateProgram() из прошлой статьи нам встречались следующие фрагменты кода:

[/crayon]
Имеется явная зависимость от типов ClassUnit и MethodUnit. А эти классы предназначены для генерации кода на C++. Если нам понадобится код на Java (с сохранением поддержки C++), то мы ничего не сможем сделать.

Указанную проблему можно решить с помощью шаблонов. Для рассмотренного примера его даже можно считать вполне приемлемой альтернативой:

[/crayon]
Однако с ростом количества поддерживаемых синтаксических структур список параметров шаблона может разрастись до огромных размеров. Поэтому хоть такое решение и имеет право на жизнь, нам оно не подходит.

Вот мы и подошли вплотную к Абстрактной фабрике. Определим соответствующий базовый класс:

[/crayon]
Назначение всех функций-членов Фабрики вполне понятно из их названий. В качестве аргументов они принимают параметры, которые будут переданы конструкторам создаваемых элементов.

Классы ClassUnit, MethodUnit и PrintOperatorUnit теперь тоже становятся абстрактными. Уберем из них реализации функции compile(), поскольку они становятся специфическими. При этом реализация add() сохраняется. Также нам приходится реализовывать функции доступа к закрытым переменным:

[/crayon]
На этом этапе мы уже можем перейти к функции-генератору на основе Абстрактной фабрики:

[/crayon]
Теперь все классы, представляющие синтаксические структуры, создаются только с помощью вызова соответствующих функций-членов Фабрики. Зависимости устранены.

Осталось только предоставить конкретные реализации классов. Начнем с реализации тех, что обеспечат поддержку синтаксиса C++:

[/crayon]
Представленный код нам уже знаком. Мы практически дословно скопировали его из прошлой статьи. Единственное отличие заключается в том, что мы вынуждены использовать специальные функции доступа к закрытым переменным базовых классов.

А вот и соответствующая фабрика в качестве заключительного штриха нашего рефакторинга:

[/crayon]
Пробный запуск:

[/crayon]
Все работает. Прекрасно. Но мы на этом не заканчиваем. Все затевалось для того, чтобы добавить поддержку генерации на еще одном языке программирования. Создадим Java-фабрику со всеми вспомогательными классами:

[/crayon]
Получившийся код более или менее работоспособен. В нем принимается в расчет специфика Java. Например, не учитывается модификатор метода const, который в Java не предусмотрен. А отсутствие модификатора virtual равносильно final-методу.

Запустим generateProgram() с этой Фабрикой:

[/crayon]
На консоль будет выведено следующее:

[/crayon]
Основная проблема заключается в отсутствии модификаторов доступа для методов. В Java модификатор должен указываться для каждого метода отдельно. Да и просто вполне логично, что он является свойством самой операции в любом языке программирования.

В качестве первого приближения расширим перечисление Modifier в классе MethodUnit:

[/crayon]
Теперь добавим соответствующую интерпретацию модификатора для JavaMethod:

[/crayon]
Чтобы все корректно работало, нужно добавить передачу флага в функции-генераторе:

[/crayon]
Теперь код на Java генерируется корректно:

[/crayon]
Для C++ тоже все работает. Но работу заканчивать все еще рано. У нас осталась одна маленькая, но очень неприятная особенность — дублирование кода. Мы передаем модификатор доступа и при создании MethodUnit, и при добавлении элемента в ClassUnit.

Мы уже остановились на том, что модификатор обязательно должен находиться в MethodUnit. Значит, нам нужно, чтобы ClassUnit умел его читать. Для этого добавим в класс Unit виртуальную функцию-член getFlags():

[/crayon]
В MethodUnit уже есть реализация этой функции. Она нас вполне устроит. Теперь проведем рефакторинг функции add() в классе ClassUnit:

[/crayon]
Обратите внимание, что объявление перечисления MethodUnit::Modifier должно быть видно в ClassUnit. Для этого нужно либо поменять порядок определения классов, либо писать код, как положено, — разделив на заголовочный файл и исходник с реализацией.

Кроме того, заметим, что теперь параметр flags во всех реализациях функции-члена add() не учитывается. Можем спокойно его убрать:

[/crayon]
Окончательно функция-генератор принимает вид:

[/crayon]
Теперь все работает. Но реализация вышла довольно многословной. Получилось много классов. Есть дублирование логики обхода дерева синтаксического разбора. Конечно, часть из этих проблем мы можем решить.

В частности, можно вернуться к идее шаблонов, но применить ее относительно самой Фабрики. Для каждого варианта шаблона, заполненного параметрами, можно предусмотреть псевдоним (определив его с помощью typdef или using). Тогда нам не придется писать реализации Фабрик, как под копирку.

Дублирование алгоритма обхода мы можем устранить с помощью еще одного паттерна — Шаблонный метод. Но и это не решит все проблемы. В полноценной системе при большом числе поддерживаемых языков программирования и синтаксических структур мы получим астрономическое число классов.

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

Тем не менее, в следующий раз мы подойдем к решению рассмотренный задачи с другого конца. Для этого мы используем более монолитный паттерн — Посетитель

Понравилась статья? Поделиться с друзьями:
Добавить комментарий