Гради Буч - Объектно-ориентированный анализ и проектирование с примерами приложений на С++ (1158635), страница 85
Текст из файла (страница 85)
По схожим соображениям, необходим также классKnowledgeSources, охватывающий все источники знаний, относящиеся крешаемой задаче:class KnowledgeSources : publicDynamicCollection<KnowledgeSource*>...Одно из свойств этого класса состоит в том, что при создании егоэкземпляра создаются также 13 специализированных источников знаний. Дляобъектов этого класса определяются три операции:•restartПерезапустить источник знаний.Задать начальные условия для•StartKnowledgeSourceисточника знаний.Связать источник знаний с доской или•connectконтроллером.Рис. 11-5.
Диаграмма классов источников знанийНа рис. 11-5 показана структура созданных в процессе проектированияклассов источников знаний.Проектирование контроллераРассмотрим более подробно взаимодействие контроллера сотдельными источниками знаний. В процессе поэтапной расшифровкикриптограммы отдельные источники знаний выявляют полезную информациюи сообщают ее контроллеру. С другой стороны, может быть обнаружено, чторанее переданная информация оказалась ложной и ее надо устранить.Поскольку все источники знаний имеют равные права, контроллер долженопросить их все, выбрать тот, информация которого кажется наиболееполезной, и разрешить ему внести изменения вызовом его операцииevaluate.Каким образом контроллер определяет, какой из источников знанийследует активизировать? Можно предложить несколько разумных правил:•Утверждение более приоритетно чем предположение.•Если кто-то говорит, что решил всю фразу, надо дать емувозможность высказаться.•Проверка по шаблону более приоритетна, чем источник,анализирующий структуру предложения.Контроллер действует в качестве агента, ответственного завзаимодействие источников знаний.Контроллер должен быть в ассоциативной связи с источниками знанийчерез класс KnowledgeSources.
Кроме того, он должен иметь в качествеодного из своих свойств коллекцию высказываний, упорядоченных поприоритету. Тем самым контроллер легко может выбрать для активизацииисточник знаний с наиболее интересным высказыванием.После изолированного анализа класса мы предлагаем ввести длякласса controller следующие операции:•resetПерезапуск контроллера.Добавить высказывание от источника•addHintзнаний.•removeHintУдалить высказывание от источниказнаний.Разрешить выполнение следующего по•processNextHintприоритету высказывания.Селектор.
Истина, если задача решена.•isSolved•UnableToProceed Селектор. Истина, если источники знанийзастряли.•connectУстанавливает связь с источникомзнаний.Все эти решения можно описать следующим образом:class Controller {public:…void reset{);void connect(Knowledgesource&);void addHint{KnowledgeSourceu);void removeHint(KnowledgeSourceu) ;void processNextHint () ;int isSolved() const;int unableToProceed() const;};Контроллер в некотором смысле управляется источниками знаний,поэтому Для описания его поведения наилучшим образом подходит схемаконечного автомата.Рассмотрим диаграмму состояний и переходов на рис.
11 -6. Из неевидно, что контроллер может находиться в одном из пяти основныхсостояний: инициализация (Initializing), выбор (Selecting), вычисление(Evaluating), тупик (Stuck) и решение (Solved). Наибольший интерес длянас представляет поведение контроллера при переходе от выбора квычислению. В состоянии selecting контроллер переходит от созданиястратегии (CreatingStrategy) к вычислению высказывания(ProcessingHint) и, в конце концов, выбирает источник знаний(SelectingKS).Рис.
11-6. Контроллер как конечный автоматДав одному из источников возможность высказаться, контроллерпереходит в состояние Evaluating, где прежде всего изменяет состояниеинформационной доски. Это вызывает переход в состояние Connecting придобавлении источника знании или к Baclctraking, если предположение неоправдалось и надо откатить его, оповестив при этом все зависимыеисточники знаний.Конечной точкой работы нашего механизма является solved (задачарешена) или stuck (тупиковая ситуация).11.3.
ЭволюцияИнтеграцияТеперь, когда ключевые абстракции предметной области выявлены,можно приступить к их соединению в действующее приложение. Мы будемреализовывать и проверять вертикальные срезы системы, а затемпоследовательно отрабатывать механизмы.Интеграция объектов верхнего уровня. На рис. 11-7 показанадиаграмма объектов нашей системы на самом верхнем уровне, котораяполностью соответствует структуре информационной доски, приведенной нарис.
11-1. Физическое содержание объектов доски в коллекцииtheBlackboard и источников знаний в коллекции theKnowledgeSourcesпоказано в соответствии с описанием вложенности классов.На диаграмме появился экземпляр класса Cryptographer. Онагрегирует объекты доски, источники знаний и контроллер. В результате нашапрограмма можетРис. 11-7.Диаграмма объектов криптоанализаиметь несколько экземпляров этого класса, а следовательно инесколько досок, функционирующих параллельно.Для этого класса следует определить две основные операции:•resetПерезапустить информационную доскуНачать дешифровку криптограммы•decipherКонструктор этого класса должен создать зависимости между доской иисточниками знаний, а также между источниками знаний и контроллером.Метод reset предельно прост: его цель состоит в том, чтобы вернуть эти связии объекты в начальное состояние.Метод decipher принимает строку - криптограмму.
Теперь функциивысокого уровня нашего приложения становятся предельно простыми, как этообычно и происходит в объектно-ориентированных системах:char* solveProblem (char* ciphertext) {Cryptographer theCryptographer;return theCryptographer.decipher(ciphertext);}Метод decipher оказывается несколько сложнее. В первую очередь спомощью операции assertProblem задание помещается на доску.
Послеэтого активизируются источники знаний. И, наконец, начинается циклическийпроцесс обращения источников знаний к контроллеру с новыми и новымипредположениями и утверждениями до тех пор, пока не будет найденорешение задачи либо процесс не зайдет в тупик. Для иллюстрации можновоспользоваться диаграммами взаимодействия или диаграммами объектов, нокод на C++ выглядит тоже не слишком сложно:theBlackboard.assertProblem();cheKnowledgeSources.reset() ;while (StheController.isSolvea ||!theController.unableToProceed() )theController.proceaaNextHint();it (theBlackboard.isSolved())return theBlackboard.retrieveSolution() ;Теперь нам лучше всего дополнить алгоритм соответствующимиархитектурными интерфейсами.
Хотя в данный момент его дееспособностьминимальна, но реализация в виде вертикального среза системнойархитектуры позволяет проверить ключевые системные решения.Посмотрим на две операции, определенные в классе decipher, аименно assertProblem и retrieveSolution. Первая из них интересна тем,что создает структуру доски. Опишем наш алгоритм следующим образом:убрать из строки все начальные и концевые пробелы ifполучилась пустая строка return создать объект-предложениезанести предложение на доску создать объект-слово (самоекрайнее слева) занести слово на доску добавить слово кпредложению for каждый символ строки слева направо if символесть пробелсделать текущее слово предыдущимсоздать объект-словозанести слово на доскудобавить слово к предложению elseсоздать объект "буква шифра"занести букву на доскудобавить букву к словуВ главе 6 уже упоминалось, что целью проектирования являетсясоздание наброска реализации.
Эта запись представляет достаточнодетализированный алгоритм, так что показывать его полную реализацию наC++ нет необходимости.Операция retrieveSolution очень проста: она возвращаетстроку,записанную в данный момент на доске.
Вызывая эту операцию до тогокак функция isSolved вернула значение True, можно получать частичныерешения.Реализация механизма предположений. Итак, мы умеем устанавливатьи извлекать значения объектов доски. Теперь нам нужен механизмвыдвижения высказывании об этих объектах. Этот механизм интересен ввидуего динамичности. При поиске решения предположения непрерывносоздаются и отзываются, чем как раз и приводится в действие весь процесс.На рис.
11-8 показан сценарий выдвижения предположений. Источникзнаний сообщает об имеющихся предположениях информационной доске,которая применяет их к алфавиту и оповещает остальные источники.В простейшем случае, чтобы отменить предположение, мы простопрокручиваем этот механизм в другую сторону. Например, чтобы отменитьпредположение о букве, мы убираем из ее коллекции все предположениявплоть до неверного.Рис. 11-8. Выдвижение предположенийМожно действовать тоньше.
Пусть мы предположили, чтооднобуквеиное слово соответствует I (нужна гласная). Далее, сделанопредположение, что некоторое двухбуквенное сочетание - это NN (нужнысогласные). Если первое предположение окажется ошибочным, то второевполне может быть сохранено. При таком подходе класс Assumption нужнодополнить еще одним методом, регистрирующим связь предположений междусобой (взаимозависимость).
Реализацию этого поведения можно отложить наболее поздний срок, поскольку оно мало влияет на архитектуру.Добавление источников знанийТеперь, когда определены ключевые абстракции информационнойдоски и механизмы выдвижения и проверки предположений, необходимореализовать механизм вывода (класс InferenceEngine), связывающий всеисточники знаний в единое целое. Ранее уже упоминалось, что механизмвывода должен реализовать одну основную операцию, а именно выполнениеправила, evaluateRules. Мы не будем на этом подробно останавливаться,поскольку реализация не влияет на проектные решения.Убедившись в правильной работе механизма вывода, можнопоследовательно вводить в систему источники знаний. Целесообразностьименно такого процесса объясняется двумя причинами:•Трудно заранее выяснить, какие правила существенны для каждого изисточников знаний, не испытав систему на конкретной задаче.•Отладка базы знаний существенно упрощается при последовательномдобавлении правил.Реализация источников знаний является предметом инженерии знаний.Для построения конкретного источника знаний требуется консультация сэкспертами (например, криптографами).