Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Сильный искусственный интеллект и объектно-ориентированное программирование синтез парадигм

.pdf
Скачиваний:
0
Добавлен:
08.09.2026
Размер:
2 Мб
Скачать
☆
базой данных имеется в виду просто некоторая структура дан­ных, а не, к примеру, 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
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]