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

GrandM-Patterns_in_Java

.pdf
Скачиваний:
98
Добавлен:
14.03.2016
Размер:
8.88 Mб
Скачать

328 Глава 8. Поведенческие шаблоны проектирования

invokeCommand вызывает метод dO l t объекта InsertStringCommand, кото­

рыЙ реально вставляет строку. Если метод dol t возвращает true, указывая, что

команда прошла успешно и может быть отменена, то объект CommancIМanager

добавляет объект InsertStr ingCommand в свою историю команд.

Приведем код, реализующий вышесказанное. Сначала - исходный код клаСса

Abs tractCommand, который является суперклассом для всех классов команд в данном примере:

public abstract class AbstractCommand ( public final static CommandМanager manager = new CommandМanager () ;

/ * *

* @returnВыполняет команду, инкапсулированную в зтом объекте . * Возвращает true при успешном выполнении

* и возможности отмены . * /

public abstract boolean doIt ( ) i

/ * *

*

Отменяет последний

вызов метода dol t .

*

@ return Возвращает

true , если отмена прошла успешно .

* /

public abstract boolean undoIt ( ) i

/ / class A b s tractCommand

Класс AbstractCommand создает экземпляр класса ComrnandManager, исполь­

зуемый для управления всеми экземплярами класса Abs tractComrnand. КОН­

кретные подклассы класса Abs tractComrnand могут получать доступ к объекту

CommandManager через переменную manager класса Abs tractComrnand.

Приведем исходный код для конкретных подклассов классаAbs tractComrnand:

lass InsertStringCommand extends AbstractCommand (

/ * *

*

Con s t ructor

* /

InsertStringCommand (Document document , int position ,

= String strng) this . document = document ;

this . position position ; this . strng = strngi

manager . invokeCommand(this) ;

/ / Con s t ructor ( Documen t , i n t , String)

Command 329

/*

*

*

*

*

*

Выполняет команду,

инкапсулированную в этом объекте .

@ return Возвращает

tru e , если этот вызов doCommand был

успешным и его можно отменять .

/

public boolean doIt() { try {

document . insertStrinqCommand (position , strng) ; catch (Exception е) {

return false ;

/ / t ry return true;

/ / do l t ( )

/ * *

 

 

 

*

Отменяет

команду,

инкапсулированную в этом объекте .

*

@ return

Возвращает

t rue , если отмена была успешной .

* /

 

 

 

public boolean undoIt ()

{

 

try

 

 

 

 

document . deleteCommand (position , strng . length (» ;

 

catch

(Exception

е)

{

 

return false ;

 

 

 

/ / try

 

 

 

 

return true ;

 

 

// undo I t ( )

// class InsertSt ringCommand

Большинство других подклассов класса AbstractCommand имеют такую же базо­

вую структуру. Исключениями являются классы для комаНд отмены и повтора.

ОНИ будут представлены в данном разделе ниже.

Теперь приведем код для класса CommandМanager, который отвечает за управ­

ление выполнением команд. Кроме того, для текстового процессора экземпля­

РЫ этого класса обязаны поддерживать историю команд для отмен и повторов.

Обратите внимание на специальное управление командами отмены и повтора.

class CommandМanager {

/ / Максимальное количество команд,

сохраняющихся в истории .

private int maxHistoryLength

= 100 ;

 

private

LinkedList history =

new LinkedList () ;

private

LinkedList redoL1st = new LinkedList ( ) ;

оГлава 8. Поведенческие шаблоны проектирования

/* *

* Вызывает команду и добавляет е е в историю .

* /

public void invokeCommand (AbstractCommand command) if (command instanceof Undo) (

undo ( ) ; return;

/ / i f undo

if (command instanceof Redo) ( redo ( ) ;

return;

/ / if redo

if (command . doIt ( »

// Метод do l t возвращает true, что означает возможность

//отмены команды .

addToHistory (command) ;

 

else ( / / Команда не может

быть

отменена ,

 

/ / поэтому история команд очищается .

 

history . clear ( ) ;

 

 

 

 

/ / i f

 

 

 

 

/ /

После выполнения команды ( кроме команд отмены или

/ /

повтора ) убеждаемся , что список команд повтора пуст .

if (redoList . size ( ) > О)

 

 

 

 

redoList . clear ( ) ;

 

 

 

/ /

invokeCommand (AbstractCommand)

 

private void undo ( )

(

( / /

 

 

if

(history . size ( ) > О)

Если

история не пуста .

 

AbstractCommand undoCmd;

 

 

 

undoCmd = (AbstractCommand) history . removeFirst ( ) ;

 

undoCmd . undoIt ( ) ;

 

 

 

 

redoList. addFirst (undoCmd)

 

 

/ / i f

 

 

 

 

/ /

undo ( )

 

 

 

 

private void redo ( )

(

 

 

 

if

(redoList . size ( ) > О )

( / /

Если

список повтора не пустой .

AbstractCommand= redoCommand;

redoCmd (AbstractCommand) redoList. removeFirst () ; redoCmd. doIt ( ) ;

history . addFirst (redoCmd)

/ / i f / / redo ( )

Command 331

/ * *

*Добавляет команду в историю команд .

*/

private void addToHistory (AbstractCommand command) { history . addFirst (command) ;

/ / Если размер истории превысил значение maxHisto ryLength, / / удаляет из истории> самую старую команду .

if (history . size () maxHistoxyLength) history . removeLast () ;

// addToHistory (AbstractCommand)

//class CommandМanager

Обратите внимание, что класс СопunапdМапаgеr в действительности не исполь­ зует классы команд, представляющие классы отмены и повтора. Он просто проверяет, реализуют ли их экземпляры интерфейсы Undo или Redo. Эти ин­ терфейсы представляют собой чистые маркеры-интерфейсы. Приведем лис­ тинг интерфейса Undo:

interface Undo

} / / inte rface Undo

Исходный код для интерфейса Redo выглядит так же.

Эти маркеры-интерфейсы предназначены для того, чтобы поддерживать класс ComrnandМanager независимым от конкретных подклассов класса AbstractCom­ mand. Поскольку класс ComrnandМanager обязан управлять отменой и повгором,

все эти классы, представляющие отмену и повтор, дают объекту ComrnandМanager

знать, что он должен отменить или повторить последнюю команду. Лучше всего,

если он будет делать это так, чтобы не требовать от класса ComrnandМanager ка­

Кой-либо специальной логики. Тогда должен использоваться механизм, который позволит объекту ComrnandМanager спрашивать, не нужно ли выполнить отмену

или повтор, а не получать извещение об этом. Если можно определить, реализует

лЖети некоторый объект интерфейс Undo или Redo, то объект ComrnandМanager мо­ спрашивать, нужно выполнить отмену или повгор, ничего не зная о классе,

реализующем этот интерфейс.

И наконец, приведем исходный код для класса UndoComrnand. Класс RedoCom­ mand отличается от него незначительно.

class UndoCommand extends AbstractCommand implements Undo {

/ * *

 

* Эта реализация метода dolt на

самом деле ничего не делает .

.. Логика для отмены находится в

классе CommandManage r .

* /

 

pub11c boolean dolt () {

 

шаб­

332 Глава 8. Поведенческие шаблоны проектироваНИR

// Этот метод не должен вызываться никогда . throw new NoSuchМethodError () ;

// do I t ( )

/* *

*

*

*

*/

Эта реализация метода undo l t на самом деле ничего не

делает .

Команды undo не отменяют . Вместо этого

выдается команда redo .

public boolean undoIt () {

// Этот метод не должен вызываться никогда .

/throw/ new NoSuchМethodError () ; undo I t ( )

/ / class UndoCommand

Поскольку не должно быть обращений к методам данного класса, эти методы всегда генерируют исключение.

ШАБЛОНЫ ПРОЕКТИРОВАНИЯ,

СВЯЗАННЫЕ С ШАБЛОНОМ COMМAND

Factory Method. Шаблон Factory Method иногда используется для установления

некоторой косвенности между пользовательским интерфейсом и классами ко­

манд.

Little Language. Шаблон Command можно использовать как вспомогательное средство при реализации шаблона Little Language. Кроме того, шаблон Little

Language может использоваться для реализации команд.

Marker Interface. Шаблон Marker Interface может использоваться вместе с

лоном Command для реализации обработки команд отмены и повтора.

Snapshot. Если нужно задать упрощенный механизм отмены, согласно которомУ

состояние объекта сохраняется полностью, а не команда за командой, то можно

ИСпользовать шаблон Snapshot.

Template Method. Шаблон Template Method может использоваться для реали­

зации логики отмены высшего уровня в рамках шаблона Соmmапд.

в основе шаблона Little Language лежит шаблон Interpreter, описанный в работе

[GoF95], и понятие малого языка, которое было популяризировано Джоном

Бентли в работе [Bentley86]. Более сложные способы проектирования и реали­ зации языков можно найти в работе [ASU86].

СИНОПСИС

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

сложных структур данных и форматирование данных - это наиболее часто встречающиеся проблемы, которые решаются при помощи малого языка.

КОНТЕКСТ

допустим, нужно написать программу, которая выполняет поиск в коллекции файлов с целью обнаружения файлов, содержащих заданную комбинацию сим­ волов или слов. Чтобы не писать отдельную проrpамму для каждой операции поиска, можно определить малый язык, при помощи которого пользователи

опишут комбинации слов, а затем написать одну программу, которая будет на­

ходить файлы с комбинациями слов, заданными при помощи малого языка.

Определение языка обычно состоит из двух частей. Синтаксис языка задает

Символы и слова, составляющие язык, и способы их комбинирования. Семантика

ЯЗыка задает значение символов, слов и их комбинаций, входящих в состав

ЯЗыка.

Синтаксис языка обычно определяется rpамматикоЙ. Грамматика - это набор

правил, определяющих последовательность символов, образующих слова и ус­

ЛОвные обозначения языка. Кроме того, грамматика содержит правила, кото­

РЫе позволяют комбинировать символы и слова языка с целью создания более

Крупных конструкций.

ТIfочное определение семантики большого языка может быть очень сложным

дЛинным. Однако для малого языка иногда вполне достаточно нескольких Простых поясняющих абзацев.

Малый сСКолько

целью

язык можно определить следующим образом. Сначала создается не­ примеров того, как язык должен выглядеть. Затем примеры обобщаются

получения окончательного определения.

334 Глава 8. Поведенческие шаблоны проектирования

Следуя этому плану, рассмотрим некоторые моменты, которые полезно учесть

в малом языке при задании комбинаций слов. Основное - это возможность за­

давать комбинацию, которая состоит из одного слова. Самый очевидный спо­

соб задания для поиска комбинации такого рода заключается в том, чтобы про­

сто написать это слово, например, так:

bottle

Кроме того, может появиться необходимость задавать комбинации слов, кото­

рые не содержат определенное слово. Это делается просто: перед словом, кото­

рого не должно быть в комбинациях слов, нужно поставить слово «not» :

not Ьох

Тогда «Ьох» .

задаются для поиска все комбинации слов, которые не содержат слова

Слова, подобные слову «(not» , которые задают определенное условие, называ­ ются зарезервированными, поскольку они предназначены для специальных це­ лей и не могут использоваться таким же образом, как обычные слова. Если слодля­ во рассматривается как зарезервированное, нельзя задать комбинацию слов поиска, содержащую такое слово, простым написанием этого слова. Однако су­ ществует способ указания комбинации слов, которая содержит любое произ­ вольное слово, последовательность слов или знаки пунктуации. Для этого та­ кую комбинацию слов заключают в кавычки:

" Ye s , not the"

Рассмотрим задание комбинации из двух слов. Очевидно, что синтаксис для та­

кой комбинации должен позволять задавать сами слова, ВХОдЯщие в комбина­ цию. Поскольку комбинировать слова можно по-разному, синтаксис должен

предоставить также способы комбинации слов. для этого необходимо написать

одно слово комбинации, далее указать специальное слово, которое показывает,

как комбинируются слова, а вслед за ним - второе слово комбинации. Напри­

мер, чтобы указать комбинации слов, содержащих хотя бы одно из слов «(bottle»

или (1al'» , можно написать следующее:

bottle or j ar

Для задания других способов сочетания двух слов понадобятся дополнительные

слова:

(,and» для указания комбинаций слов, содержащих оба слова;

«near» для указания комбинаций слов, в которых между указанными слова-

ми могут находиться до 25 каких-то других слов.

Если нужно, например, использовать зарезервированное слово (,and» вместе

с зарезервированным словом (,not» для указания комбинации слов, содержашей ('garlic» и не содержащей <юпiопs» , необходимо будет написать

gar l i c and not onions

Little Language 8 335

Эти примеры охватывают почти все, что нужно для описания комбинаций, со­

держащих два слова. При использовании более двух слов потребуется рассмот­ рение дополнительных условий. Очевидно, что

red алd " pickup t ruck" алd Ьrоkел

означает комбинации слов, содержащих все три элемента: слово «red» , фразу

«pickup truck» и слово «broken».

Если смешиваются разные способы сочетания слов, цель становится неодно­ значноЙ. Словосочетание

turkey or сhiсkел алd soup

указывает на комбинацию слов, содержащую слово «turkey» или оба слова «chicken» и «SOUP» ? Или оно описывает комбинацию слов, содержащую слово «soup» И хотя бы одно из слов «chicken» или «turkey» ? Чтобы преодолеть эту не­ однозначность, нужно поставить скобки, определяющие порядок использова­ ния логических связей в комбинации слов. Для задания первого варианта ин­ терпретации можно написать:

turkey or ( сhiсkел алd soup )

Для указания второго варианта можно написать:

( tu rkey or сhiсkел) алd soup

Большинству пользователей не нравится, когда их заставляют писать скобки, поэтому желательно найти способ решения проблемы без использования ско­ бок. Для устранения неоднозначности такого рода в определениях языка широ­ ко применяется правило nредшесmвованuя, которое назначает приоритеты раз­ личным используемым в языке операциям. Его суть заключается в том, чтобы

предоставить способ определения порядка операций. Операции с более высо­ ким приоритетом выполняются перед операциями с более низким приорите­

том. Допустим, назначают следующие значения приоритетов:

near 3

and 2 or 1

ПРИ таком определении значений приоритетов словосочетание

mалisол or big леаr house алd rich

задаети комбинации слов, содержашие слово «manison» или оба слова «rich» «big» при условии, что расстояние между словом «big» И словом «house» не

nревышает 25 слов.

Если бы

Образом:

mал i s

использовались скобки,

оn or ( (big near house)

то такое условие выглядело бы следующим

and rich)

Перед тем как спроектировать классы и придать смысл малому языку, важно

Описать грамматику, которая определит синтаксис языка. На основе такого

336

Глава

8.

Повед

ен

ческие шаблоны проектирования

 

 

 

 

описания можно начинать кодирование. Грамматика может быть организована

в соответствии с различными стратегиями. Во всех примерах данного шаБЛОна

используется нисходящая стратегия. Это значит, что начинают с языковой кон­

струкции самого верхнего уровня - комбинации слов - и затем определяютдо

все конструкции более низких уровней, которые могут входить в ее состав,

тех пор пока грамматический разбор не будет завершен полностью.

Выше уровня отдельных символов находится уровень конструкций, образуЮ­

щих грамматику и называющихся лексемами. Лексемы бывают либо терминаль­

ными, либо нетерминальными. Терминальные лексемы соответствуют непре­

рывной последовательности символов, а также строкам в кавычках и скобках.

Наприfenceмер, слово в виде

представляет собой терминальную лексему. Конструкции более высокого уров­

ня, состоящие из терминальныхлексем, называются нетерминальными лексема­ ми. Комбинация слов - это нетерминальная лексема.

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

Правила, позволяющие распознавать последовательности символов как терми­ нальные лексемы, называются nравuламилексического анализа. Правила, позво­ ляющие распознавать нетерминальные лексемы в виде последовательностей терминальных и нетерминальных лексем, называются продукциями.

Система обозначений, используемая в данной книге для записи продукций, на­ зывается фор.!иоЙ Бэкуса-Наура. Согласно этой форме терминальные и нетерми­ нальные лексемы записываются разными шрифтами, что позволяет различать

их. В этой книге терминальные лексемы указываются так:

quoted_string

а нетерминальные:

combination

Продукция, содержащая нетерминальную лексему и последовательность тер­

минальных и нетерминальных лексем, может быть распознана как первая не­

терминальная лексема. Приведем пример продукции: combina t i on word

Эта продукция говорит о том, что нетерминальная лексема комбинации моЖет

содержать просто терминальную лексему слова.

Если сушествует несколько последовательностей лексем, которые могут быть

распознаны как нетерминальная лексема, то

л

ь

КО

будет существовать неско

 

продукций для такой нетерминальной лексемы.

для каждой последовательносТИ.

которая может быть

распозна

на как такая нетерминальная лексема,

е

 

будет суш

ствоватьдляодна продукция. Например, следуюший набор продукций задает сиН таксис комбинаций, которые содержат или не содержат конкретное слово.

- ­

Little Language - 337

combina t i on word

combina tion not word

Способ, который используется для определения того, что нетерминальная лек­ сема должна распознаваться из последовательности лексем неопределенной

Д)1ины, называется рекурсией. Приведем описание набора продукций, который

охватывает почти весь синтаксис предыдущих примеров:

combination ( combina tion )

 

combina tion simpleCombina tion

 

combination simpleCombination or combina tion

combination simpleCombination and

combina t ion

combination simрl еСоmbiла tiоn near

combination

simpleCombina tion word

 

simpleCombinationчто not word для

Обратите внимание, четыре продукции из пяти combina ti оп являются

рекурсивными. Три продукции из этих четырех могли быть записаны при помо­

щи combina tion в качестве- первой нетерминальной лексемы и при помощи simpleCombination в качестве второй нетерминальной лексемы. В любом случае они должны соответствовать одним и тем же последовательностям лек­ сем. Однако для способа реализации, описанного ниже в данном разделе при включении продукций в код, разница существует. Для того способа, который будет рассматриваться, доказано, что при написании продукций лучше всего

использовать правую рекурсию. Правая рекурсия означает, что, если есть воз­

можность выбора, в каком месте продукции применить рекурсию, мы должны

выбрать такой вариант, когда рекурсия используется как можно правее.

Хотя этот набор продукций не описывает всех деталей данного языка словосо­ четаний, содержащейся в нем инФормации достаточно для того, чтобы можно было работать, используя пример, приведенный в книге. Теперь рассмотрим,

каким образом использовать такие продукции для распознавания следующей

Строки как комбинации

fox and not brown

Взглянув на продукции для cOmbina tion, можно увидеть, что combination

Может начинаться либо с открывающей скобки, либо с simpleCombina tion.

Строка начинается с лексемы word. Поскольку строка, которую мы пыаемсяя

распознать как combina tion, не начинается с открывающей скобки, мы пыта­

еМСЯ распознать начало строки как simpleCombination. Это соответствует

Продукции

simpleCombination word

у нас распозналась следующая часть строки:

fox and not brown

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]