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

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

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

помощи паттерна «Компоновщик». Космические системы организованы точно таким же образом: отдельные планеты в данном
случае выступают листами, звездные системы представляют
собой составные объекты, узлы, галактики – еще большие узлы,
а интерфейс всей этой структурной организованности задан самой Вселенной с ее законами физики – компонентом, как эле-
ментом паттерна «Компоновщик». Про вложенные структуры мы
также говорили ранее в контексте фрактальной геометрии. Таким образом, целесообразно будет резюмировать, провозглашая
фундаментальность тех абстракций, которые несколько конкретизирует паттерн «Компоновщик» в контексте своей локальной
реализации «на местах». Относительно же сугубо практической
значимости данного паттерна при формировании систем сильного ИИ и внедрения абстракций, аккумулируемых паттерном,
непосредственно в процесс разработки, стоит отдельно дополнить, что нами предполагается буквально следующее: система,
которая построена на естественных фундаментальных принципах самоорганизации, УЖЕ реализует суть паттерна «Компоновщик».
13.5. Структурный паттерн «Декоратор»
Идентификатор. Декоратор (Decorator).
Классические элементы. Декоратор, компонент. В класси-
ческом понимании данного паттерна добавляются еще два элемента: конкретный декоратор и конкретный компонент. Однако
это не изменяет сути паттерна и ничего существенно значимого
не добавляет к его структуре.
Назначение. В Design patterns указано, что предназначение
«Декоратора» сводится к тому, что он «динамически добавляет
объекту новые обязанности. Является гибкой альтернативой порождению подклассов с целью расширения функциональности»
[114, с. 214]. Для уточнения: в данном случае имеются в виду не
только и не столько обязанности, сколько возможности. Таким
образом, данный паттерн в некотором роде предназначен улучшать функционирование некоего ПК новыми дополнительными
244

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

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

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

Осмысление структуры паттерна. Данный паттерн обладает довольно эксплицитной структурой связей: компонент свя-
зан с декоратором по типу композиции в том смысле, что сам
компонент может существовать отдельно от декоратора, а деко-
ратор связан с компонентом по типу агрегации, так как сам декоратор создается для компонента и отдельно от него не суще-
ствует.
Значимость паттерна в контексте разработки систем
сильного ИИ. Данный паттерн является весьма тесно связан-
ным не только с парадигмой сильного ИИ, но и с разработками
в сфере ИИ вообще. Любые интеллектуальные системы, существующие на данный момент, вне зависимости от конкретной
реализации, т. е. вообще все системы слабого ИИ, являют собой,
по сути, декораторы. Более того, именно в данном качестве они
изначально и проектировались, так как все они предназначены
для своеобразного оказания помощи человеку и облегчения выполнения им каких-либо задач. Собственно говоря, в большинстве случаев точно такая же роль вменяется и гипотетическому
сильному ИИ: система, на субстрате которой, возможно, возникнет разум, редуцируется до роли «помощника». Само собой разумеется, что у системы, которая на самом деле будет обладать
техноропным сознанием (сознание иного типа у них не подразумевается), на этот счет может быть совсем другое мнение. И из
сценария перетекания «помощника» в его условную противоположность рождаются уже постапокалиптические прогнозы. Нам
же представляется, как уже не раз упоминалось, что разработчики системы сильного ИИ не имеют непротиворечивого и логически обоснованного права решать хоть что-то за систему (это
не ребенок, которого надо воспитывать, это то, о сущ ности чего
мы не можем точно что-либо постулировать).
Исходя из вышесказанного, значимость паттерна «Декоратор» в контексте парадигмы сильного ИИ нам видится в, опять
же, абстракции паттерна – обертывании. Так как данная абстракция является весьма эффективной, что подтверждается
в плане всеобщности ее реализации (значит, это абстракция с претензией на фундаментальность), мы считаем, что ее также необ-
248

ходимо зафиксировать в виде возможности реализации со стороны системы функционала, структурированного по типу обер тывания одних ответственностей в другие, одних функций
в другие, одних компонентов в другие и так далее – не важна
конкретная реализация, а важно соблюдение принципа.
13.6. Структурный паттерн «Фасад»
Идентификатор. Фасад (Facade).
Классические элементы. Фасад, классы подсистемы.
Назначение. В Design patterns указано, что предназначение
«Фасада» сводится к тому, что он «предоставляет унифицированный интерфейс вместо набора интерфейсов некоторой подсистемы» [114, с. 189]. Использование паттерна «Фасад» в общем
смысле способно предоставить некоторый высокоуровневый интерфейс к сложной разрозненной системе, что должно существенно облегчить взаимодействие с этой системой.
Проблема. Допустим, у нас имеется большая система, состоящая из многих ПК. Компоненты объединены в группы, и каждая группа компонентов решает какие-либо задачи. Данная система представляет очень широкий функционал, и им можно
пользоваться для различных целей. Проблема заключается в том,
что с подобной системой очень сложно взаимодействовать. И эта
проблема особенно обостряется в том случае, если нам необходим лишь ограниченный функционал этой большой системы.
Для условного примера: система предоставляет возможности
A, B, C, D, E, J, K, L. Нас же почти во всех случаях интересуют
только возможности A, D, J. Нам бы хотелось каким-то образом
сделать так, чтобы мы могли использовать только интересующие
нас возможности, не имея при этом необходимости разбираться
с интерфейсом программной системы в целом, однако сохраняя
для себя такую возможность на тот случай, если нам понадобится дополнительная функциональность, предоставляемая системой. Т. е. нам в некотором роде необходим определенный
фреймворк.
249

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