Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Сильный искусственный интеллект и объектно-ориентированное программирование синтез парадигм
.pdf
базой данных имеется в виду просто некоторая структура данных, а не, к примеру, MySQL), тем самым осуществляя подготовку к эффективному подбору упражнений со стороны алгоритма. После фильтрации контекст вызывает одну из трех
конкретных стратегий («масса», «сила», «сушка»), передавая ей
доступ к обновленной базе данных, а также передавая в качестве
аргументов данные, введенные пользователем. Далее конкретная стратегия осуществляет подборку определенных упражнений в соответствии с имеющимся у нее алгоритмом, а затем возвращает уже подобранную программу тренировки с описанием
упражнений, количества подходов, повторений, временем отдыха между подходами и тренировками и т. д. Как видно из примера, ответственности распределены, алгоритмы и логика подборки инкапсулированы, единый для всех конкретных стратегий
интерфейс определен (его определяет стратегия), фундаментальные принципы соблюдены, структурные и организационные – также. Система функционирует со всеми необходимыми
для эффективной работы особенностями, и в том случае, если
нам понадобится добавить какую-либо новую функциональность в плане конкретных стратегий (например, тренировка
«на силовую выносливость» или «взрывную силу»), нам просто
нужно будет создать подкласс класса стратегия и инкапсулировать в него алгоритм подбора. Это – пример хорошей реализации паттерна «Стратегия» на практике.
Осмысление структуры паттерна. Структура данного паттерна не является особенно запутанной и слишком динамичной,
напротив, роли каждого ПК четко очерчены и в runtime не трансформируются. В runtime может осуществляться только смена
контекста и, как следствие, смена конкретной стратегии, о чем,
собственно, и говорилось в цитате из Design patterns. Очевидно,
что конкретные стратегии связаны со стратегией отношениями наследования. Контекст же связан со стратегией (в классическом понимании) двусторонними отношениями. Со стороны
контекста отношения строятся по типу агрегирования, так как
без стратегии контекст бесполезен и не сможет существовать,
а со стороны стратегии типом отношений является компози-
201

ция, так как сама стратегия и являет собой центральный ПК
всего паттерна (достаточно вспомнить наш пример «плохой»
программной системы, в которой стратегия была вообще единственным программным компонентом).
Значимость паттерна в контексте разработки систем
сильного ИИ. Если расценивать паттерн «Стратегия» с точки
зрения его полезности для разработки систем сильного ИИ, то
смена стратегии поведения вслед за изменившейся ситуацией
смотрится примерно таким же образом, каким выглядит смена
поведения в зависимости от настроения (как в паттерне «Состояние»), т. е. кажется, в общем-то, абсолютно тривиальной и по
умолчанию подразумевающейся. И так действительно и есть. Но
если несколько абстрагировать суть паттерна «Стратегия», то
станет видно, что она – суть паттерна – заключается не в утверждении того, что необходимо изменять стратегию действий вслед
за изменением контекста (еще раз – это и так понятно), а в том
способе, при помощи которого это реализуется. А именно (примерно так же, как и в случае с паттерном «Команда») – суть
в инкапсуляции. А также в некоторой (пусть и очень обобщенной) реализации полиморфизма, который, как мы помним, в числе прочего представляет собой способность дочерних классов
быть использованными в контексте, который изначально предназначался для класса родительского. Т. е. мы, в контексте
структуры паттерна и специфики его реализации, видим некоторый уже готовый итог развития системы. И выражается это
в том, что мы от плохо дифференцированной, большой и неудобно функционирующей системы, представленной одним «неповоротливым» программным компонентом, переходим к логически
непротиворечиво организованной, структурно оформленной и высокоэффективной системе, представленной некоторым количеством разнообразных ПК, каждый из которых выполняет свою
задачу и несет на себе свою ответственность. И эта система хорошо работает в целом. Если ненадолго «забежать на верхний
этаж абстракции», то можно будет постулировать, что именно
способом, подобным вышеописанному, из хаоса и получается
нечто упорядоченное. В случае с данным паттерном это самое
202

«упорядоченное» представлено совокупностью неких акторов,
т. е. субъектов (как ни странно, представленных объектами),
расположенных в строгой иерархии и разделенных по обязанностям и ответственностям. И организованность подобного рода
была достигнута в процессе «эволюции» программной системы
«от хаоса к упорядоченному». Это, опять же, может нечто напоминать и, разумеется, напоминает. А именно – то, как мы себе
представляем развитие системы сильного ИИ. Примерно таким
образом нам и видится его эволюция. Т. е. из изначально не
вполне четко определенной системы, обладающей некоторым
потенциалом (только лишь потенциалом, а не его конкретной
реализацией) формирования в себе тех или иных механизмов,
по итогу ее развития получается эффективно функционирующая
сложная, распределенная, иерархически оформленная система,
все ПК которой несут в себе свой функционал, успешно справляются со своей ответственностью и действуют в синергии.
В рамках ООП способы, которыми примерно подобный уровень
достигается в случае с данным поведенческим паттерном, – это
инкапсуляция и полиморфизм (отдельно про наследование, абстракцию и интерфейс мы здесь не говорим, так как это является и без того очевидным). И не вызывает никаких сомнений тот
факт, что и тот, и другой ключевой аспект ООП будут представлены на фундаментальном уровне организации системы сильного ИИ. И именно конкретную реализацию в контексте данного
паттерна мы и считаем необходимым использовать.
12.8. Поведенческий паттерн «Итератор»
Идентификатор. Итератор (Iterator).
Классические элементы. Итератор, конкретный итератор,
агрегат, конкретный агрегат.
Назначение. Как указывается в Design patterns, «Итератор»
«абстрагирует… технику поддержки обхода структур, состоящих из объектов, и доступа к их элементам. Он применим не
только к составным структурам, но и к группам, абстрагирует
алгоритм обхода и экранирует клиентов от деталей внутренней
203

структуры объектов, которые они обходят» [114, с. 81]. Паттерн,
как и следует из идентификатора, предназначен для обхода
структур данных с, так сказать, внешней точки зрения, т. е. снаружи, без нарушения и всякого трансформирования внутрен ней
организации самой структуры данных. Последнее представляется весьма значимым фактором и основной детерминантой исследования данного паттерна. Ведь с некоторым внедрением
в структуру ПК, который представлен структурой данных, итерирование было бы возможно осуществить и без паттерна «Итератор» вовсе, просто добавив к функционалу этого компонента
дополнительно еще и метод итерирования.
Проблема. Основная проблема, которую предназначен решать паттерн «Итератор», заключается в необходимости наличия
разнообразных способов осуществления прохода по элементам
некоторой структуры данных, причем инвазивно, не затрагивая
саму структуру и не трансформируя ее специфическую организацию. Приведем до известной степени утрированный пример.
Можно себе представить, что у нас есть некий список каких-либо значений (без уточнения сущности как значений, так и самого списка). И вот нам необходимо, чтобы мы, при желании найти
в этом списке какое-нибудь значение, могли пройти по нему
с начала и до конца (или пока не обнаружим искомое значение).
Мы добавляем эту функциональность в виде отдельного метода
в структуру ПК, содержащего эти самые значения. Затем предположим, что мы хотим также при поиске какого-либо значения
пройти по нашей структуре данных, но уже с конца в начало.
Мы реализуем логику и этого метода и также добавляем его
в общий функционал нашей структуры данных. Затем нам вдруг
стало необходимо найти некоторое значение при помощи последовательного итерирования по элементам нашей структуры, проходя с начала до конца, но переступая через один элемент,
т. е. затрагивая только каждый второй. Допустим, что мы и этот
метод добавили к общему функционалу ПК. И так, собственно,
далее – продолжать можно очень долго. Но, как окажется через
некоторое время, подобный путь расширения функциональности ПК, представляющего некую структуру данных, ведет
204

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

струировать этот специфический способ. Выходит, что организованность и инкапсуляция нашей структуры данных не нарушаются, а вся логика итерирования инкапсулирована в отдельный ПК.
Предполагаемые результаты и преимущества. Отсутствие
нарушений принципа сильной связности и принципа единственной ответственности. Распределение ответственностей между
несколькими ПК, действующими сообща. Поддержка очень
широкого спектра возможностей итерирования по различным
структурам данных. Причем становится незначительным, что
именно это за структура и что именно за способ, так как конечный интерфейс одинаков для всех. Т. е. если ранее для итерирования по различным структурам данных должны были применяться различные способы, то теперь для итерирования по
любой структуре данных применяются одни и те же, потому что
при обращении к структуре данных с запросом на итерирование
она просто отдаст объект-итератор, который одинаков для всех
структур данных.
Гипотетический пример реализации паттерна. Ввиду
весьма узкой специализации данного поведенческого паттерна
приведем приближенный пример. Предположим, что у нас наличествует несколько наборов каких-либо элементов. Нам необходимо отыскать в каждом из наборов некоторый элемент, на
котором, допустим, присутствует интересующий нас условный
символ. Однако все элементы всех коллекций перевернуты интересующей нас стороной вниз. Проблем сразу две. А именно –
разнородность и неупорядоченность элементов в разных наборах и отсутствие унифицированного способа перебора этих
данных. Казалось бы, что это не такие уж серьезные проблемы,
но дополним пример тем, что наборы весьма большие по количеству и представлены следующими элементами: бетонные плиты весом в несколько тонн, ведра с ртутью и урановые стержни.
Т. е. искать элемент вручную представляется не лучшей идеей.
Однако тут мы узнаем, что у всех коллекций имеется некоторая
документация в виде отражающих всю информацию об элементах карточках, на которых написано, что это за элемент и что он
206

содержит. Это, разумеется, уже сильно меняет дело, так как ввиду унификации всех данных нам не придется использовать различные способы для переворачивания бетонных плит, подъема
ведер с ртутью и манипуляций с урановыми стержнями. Приведенная ситуация представляет собой метафоричное воплощение
создания объекта-итератора. Однако остается проблема большого количества этих карточек в каждой из коллекций. Причем
в случае с плитами и ведрами нам отчего-то представляется,
что искомый нами условный символ расположен где-то ближе
к началу коллекции, а в случае со стержнями нам думается, что
он, наоборот, ближе к концу коллекции. Т. е. мы хотим еще
и искать по-разному. К тому же мы прекрасно понимаем, что перебирать вручную все коллекции ужасно долго. Поэтому мы
приглашаем трех помощников, каждому из которых объясняем
задачу и даем инструкции, в которых указываем, что именно мы
ищем и с какой стороны стопки следует начать. Само собой, так
будет в три раза быстрее. Приближенное метафоричное определение создания собственного итератора. Резюмируя пример,
будет целесообразным заметить, что конечная ситуация очень
даже выгодно отличается от наших изначальных условий. Таким образом и реализуется суть «Итератора».
Осмысление структуры паттерна. Структура данного паттерна сложной не представляется. Конкретный итератор является подклассом итератора и связан с ним наследственными
связями точно таким же образом, как и конкретный агрегат
с агрегатом. Агрегат с итератором связываются при помощи
пользователя, а конкретный агрегат и конкретный итератор
связаны, соответственно, при помощи обоюдостороннего агрегирования.
Значимость паттерна в контексте разработки систем
сильного ИИ. Поведенческий паттерн «Итератор» в контексте
разработки систем сильного ИИ может носить в первую очередь
утилитарный характер. Т. е. абстракции, лежащие в основе паттерна, должны быть использованы для работы системы с некоторыми множествами доступных ей элементов путем унификации представлений этих множеств и нахождения наиболее
207

эффективного способа поиска в каждом конкретном множестве
необходимой информации. Также понятным является, что все
способы перебора, доступные системе, должны быть унифицированы (обладать единым интерфейсом).
12.9. Поведенческий паттерн «Шаблонный метод»
Идентификатор. Шаблонный метод (Template method).
Классические элементы. Абстрактный класс, конкретный
класс.
Назначение. Как указывается в Design patterns, «Шаблонный метод» «определяет основу алгоритма и позволяет подклассам переопределить некоторые шаги алгоритма, не изменяя его
структуру в целом» [114, с. 309]. Данный паттерн предназначен
для стандартизации и унификации неких последовательностей
действий (можно сказать, паттернов действий) таким образом,
чтобы основа всех попадающих под некоторый критерий последовательностей действий была одинаковой, а реализации конкретных шагов различались в зависимости от контекста.
Проблема. Представим себе некую систему с определенным
количеством ПК, каждый из которых выполняет различные действия. Для каждого из этих компонентов определена («с нуля»)
некоторая логика действий в определенной последовательности.
К примеру, первый ПК должен итерироваться по массиву и возвращать некое значение, предварительно преобразовав его в JSONформат, второй – по связному списку и возвращать некое значение, предварительно преобразовав его в строку, третий – по бинарному дереву поиска и возвращать значение в ASCII-формате,
четвертый – по словарю с последующим возвратом значения
в виде массива и т. д. Они выполняют разные действия и, опять
же, в каждом из них инкапсулирована его собственная логика.
В целом ничего страшного пока не происходит. Однако мы вполне могли бы существенно улучшить функционирование подобной
системы и сократить количество прописанной «с нуля» логики.
Для этого нам нужно внимательнее посмотреть на реализацию
своего функционала каждым программным компонентом. Мы
208

можем заметить некоторые общие детали. А именно: каждый
ПК получает «на вход» некоторую структуру данных и какое-то
искомое значение, затем каждый компонент «пробегает» по своей структуре данных, а затем возвращает некое значение (в данном случае не существенно, какое именно). И мы бы хотели каким-то образом сделать так, чтобы нам не было необходимо
реализовывать каждый из этих ПК «с нуля», учитывая тот факт,
что этапы реализации функционала схожи у всех этих компонентов. В данном случае на помощь и приходит паттерн «Шаблонный метод».
Концептуальное решение. В качестве решения мы можем
поступить так. В рамках нашей системы определим некоторый
базовый ПК, который будет представлен абстрактным классом.
Как мы помним, абстрактный класс – это класс, который только
определяет (т. е., в рамках терминологии программирования, по
сути, объявляет) свои методы, но не реализует их (опять же,
в рамках терминологии – детально не прописывает логику).
Именно такой класс нам и необходим. Мы определяем в нем те
методы, которые хотим далее реализовать. В случае с примером
выше мы могли бы определить (условно) метод итерирования по
получаемой в качестве аргумента структуре данных с целью
найти в ней некоторое, также получаемое методом в качестве аргумента, значение. Затем мы определим метод преобразования
значения в необходимый формат. Еще раз – эти методы мы только объявили в абстрактном классе, т. е. мы не станем здесь их
реализовывать. Далее мы объявляем и теперь уже определяем
метод (непосредственно сам шаблонный метод как дополнительный элемент паттерна), который последовательно вызовет
с необходимыми аргументами сначала метод итерирования по
структуре данных с целью найти значение, а затем метод преобразования этого значения с его дальнейшим возвратом. Далее
мы наследуем от этого базового ПК все те компоненты (кон-
кретные классы), которым в процессе функционирования предстоит осуществлять вышеописанные этапы. Каждый из наследующих компонентов у себя локально определяет метод итерирования по связанной (условно) с ним структуре данных
209

с учетом специфики именно этой структуры. Далее он, также
локально у себя, определяет метод преобразования найденного
значения в необходимый формат. И после этого вызывает метод
базового класса, который вызывает уже переопределенные методы подкласса, причем вызывает их в строгой последовательности. Таким образом, мы в базовом программном компоненте
определяем общую структуру паттерна и делегируем некоторую
часть реализации его подкомпонентам, а затем просто вызываем
поочередно переопределенные ими «на местах» методы.
Предполагаемые результаты и преимущества. Реализация
«Шаблонного метода» позволяет существенно сократить (в идеале – избежать) повторное определение одной и той же логики
и объединить группу схожих ПК в единую структуру, а также
легко и быстро добавлять новую функциональность в систему.
Гипотетический пример реализации паттерна. В случае
с данным паттерном в качестве примера очень хорошо подходят
различные пошаговые инструкции. Можно подобрать какуюнибудь не совсем стандартную. Например, инструкцию по стрельбе в тире. Допустим, в ней указаны следующие шаги: возьмите
оружие, возьмите боеприпас, подойдите к линии стрельбы, зарядите оружие, встаньте на изготовку, прицельтесь, поразите
мишень, подойдите к мишени, вернитесь за линию, сдайте оружие. Все шаги определены четко и последовательно. Однако для
каждого вида оружия каждый шаг будет полностью специфическим, и он не определен в общей инструкции, а содержится
в инструкции непосредственно к конкретному оружию, где прописано, как его заряжать, как его брать на изготовку, как целиться и т. д. Можно также привести пример по заливке бетонной
площадки. Там тоже определены все последовательные шаги:
расчистить площадку, установить опалубку, постелить полиэтилен, связать арматуру, установить маяки, залить площадку, через
некоторое время полить водой. Однако каждый из приведенных
шагов для каждой площадки будет переопределяться локально,
«на месте».
Осмысление структуры паттерна. Паттерн «Шаблонный
метод» отличается довольно простой структурой. В его рамках
210
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
