Анимация
JavaScript


Главная  Библионтека 

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 [ 46 ] 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82

Изменившиеся определения template <c1ass Type>

LockPtr<Type>::LockPtr(Transaction* t, ConstPtr<Type>* cp) : transaction(t), master ptr(cp), new image(cp->o1d image != NULL ? new Type(*(cp->o1d image)) : NULL)

template <c1ass Type>

void LockPtr<Type>::Commit()

delete master ptr->o1d image; master ptr->o1d image = new image; if (new image != NULL)

new image = new Type(*new image);

template <c1ass Type>

Type* LockPtr<Type>::operator->() const

if (new image == NULL)

исключение return new image;

template <c1ass Type> void LockPtr<Type>::Make()

delete new image; Если new image не равен NULL

new image = new Type; Новый пустой объект

template <c1ass Type>

void LockPtr<Type>::Destroy()

delete new image; new image = NULL;

Функция Make() соблюдает семантику присваивания, что позволяет вызвать ее для существующего указываемого объекта. При этом объект, на который в данный момент ссылается LockPtr, уничтожается и заменяется новым пустым объектом.

Упрощенное создание объектов

Объекты теперь создаются в три этапа:

1. Создать ConstPtr, указывающий на NULL.

2. Запросить у него Lock.

3. Потребовать у Lock создать объект функцией Make().

Конечно, это произведет впечатление на ваших коллег и лишний раз докажет вашу техническую квалификацию, но они косо посмотрят на вас и вернутся к оператору new. Ведь он справляется с задачей за один этап, а нас окружают занятые, очень занятые люди. Существует несколько способов свести процесс создания к одному этапу, и самый простой из них - включить в ConstPtr другой конструктор.



ConstPtr<Type>::ConstPtr(Transaction* t) : o1d image(NULL), lock(NULL)

LockPtr<Type>& 1p = Lock(t); 1p.Make();

Последующий вызов Lock() возвращает уже созданный LockPtr.

Отмена

Все до смешного просто. Если вы внимательно следили за тем, как развивается тема транзакций, то для безопасной и универсальной реализации отмены вам потребуется совсем немного - включить необходимые фрагменты из вашей любимой библиотеки классов с графическим интерфейсом пользователя. Для создания и манипуляций со структурами данных транзакций идеально подходят классы событий. Объект-событие создается для выполнения некоторой команды или внесения изменений. Например, пользователь выбирает команду Delete в меню Edit или нажимает клавишу Delete на клавиатуре. Объект создается на базе класса, который прекрасно разбирается в удалениях. В дальнейшем этот объект не только вносит затребованные изменения, но и обеспечивает работу команды Undo меню Edit. Создайте транзакцию в его конструкторе, воспользуйтесь транзакцией для сохранения всех изменений, а затем - для поддержки отмены.

Варианты

Мы не будем пережовывать дополнительный код, относящийся к транзакциям. Если вы еще не уловили общий принцип, бессмысленно подливать масло в огонь, а если уловили, то без труда реализуете все, о чем говорится ниже. Так что я ограничусь лишь замечаниями, в которых описываются некоторые важные вариации на данную тему.

Вложенные блокировки

Чтобы вся методика имела хоть какой-то смысл, мы должны спрятать ключи от указываемых объектов и заставить всех работать с ContrPtr и LockPtr. Это относится не только к изменениям, вносимым в указываемые объекты непосредственно клиентами, но и к тем, которые один указываемый объект вносит в другой. Таким образом, в общем случае указываемые объекты должны обращаться друг к другу через ConstPtr и LockPtr, как и клиенты.

class A { private:

ConstPtr<B>& b; ConstPtr, как выше И т.д.

Если экземпляр класса A захочет вызвать неконстантную функцию b, он должен сначала получить LockPtr для b. Таким образом, класс A должен знать, какую транзакцию он выполняет при попытке изменения b, и эта информация должна поступить от клиента. Для простоты назовем объекты, к которым клиенты обращаются напрямую, первичными, а объекты, косвенно обновляемые указываемым объектом, - вторичными по отношению к указываемому объекту (один и тот же объект может одновременно быть и первичным, и вторичным).

Если вторичный объект инкапсулируется в первичном (то есть вторичный объект невидим для окружающего мира), это правило можно обойти. Конструктор копий первичного объекта должен продублировать вторичный объект, а оператор = первичного объекта должен продублировать вторичный объект правостороннего выражения. При соблюдении этих условий логика создания образов в ConstPtr и LockPtr будет правильно работать с инкапсулированными объектами. Это происходит автоматически в тех случаях, когда вторичный объект внедряется в первичный (то есть когда в каждом экземпляре A объект B хранится не как указатель, а как обычная переменная класса):

class A { private:



B b; Внедренный объект

Компилятор будет автоматически вызывать конструктор копий B при каждом копировании A, и оператор = класса B - при выполнении присваивания.

Предположим, класс A должен заблокировать b. Откуда берется Transaction*? Разумеется, извне A - то есть от клиента. Впрочем, эту задачу можно решить тремя способами:

• Передавать Transaction* в каждую функцию A, которой может потребоваться вызвать неконстантную функцию B.

• Один раз передать Transaction* в A во время блокировки и сохранить в переменной класса.

• Сделать указатель ConstPtr класса A еще более гениальным - научить его блокировать вторичные объекты по поручению A.

На последнем решении следует повесить табличку: «Только для профессиональных каскадеров. Не уверен - не пытайся». Тем не менее, в некоторых ситуациях лучшая реализация транзакций подразумевает использование коммерческих классов указываемых объектов. Этой стратегией можно воспользоваться даже для того, чтобы разрешить A хранить его обожаемый B* и ничего не знать о ConstPtr и LockPtr. Поумневшие ConstPtr и/или LockPtr класса A могут без его ведома менять адреса при создании образов и закреплении изменений. В дальнейшем мы еще неоднократно встретимся с концепцией объектов, которые описывают другие объекты и/или манипулируют ими. Ближе к концу мы снова вернемся к проблеме межобъектных ссылок.

Взаимные блокировки и очереди

Если в любой момент времени может существовать не более одной транзакции, все замечательно. Если же с вашими объектами могут возиться сразу несколько транзакций, придется сделать еще несколько шагов. Возможны разные причины - например, ваше приложение-сервер может обслуживать несколько пользователей, или вы напишете графическое приложение с контекстной отменой, в котором необходимо запоминать отменяемую команду для нескольких окон. Наконец, остается еще одно препятствие - необходимо продумать поведение вашей программы в ситуации, когда она пытается заблокировать уже заблокированный объект.

Консервативная блокировка

При консервативной блокировке (conservative locking) транзакция блокирует все объекты, которые ей могут понадобиться, до внесения каких-либо изменений. Если заблокировать все объекты не удается, транзакция либо ждет, пока они станут доступными, либо поднимает руки и сообщает пользователю, что при всем уважении к нему просьба отклоняется. Одна из возможных реализаций консервативной блокировки заключается в том, чтобы субклассировать Transaction и попытаться заблокировать все необходимое в конструкторе производного класса (при наличии стандартной обработки исключений) или в его отдельной инициализирующей функции (при отсутствии такой обработки).

Агрессивная блокировка

При агрессивной блокировке (aggressive locking) транзакция может начинаться в любой момент и блокировать объекты по мере необходимости. Я бы назвал ее «своевременной» блокировкой, поскольку блокируются только непосредственно обновляемые объекты и это происходит перед первым обновлением.

Очереди и взаимные блокировки

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



0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 [ 46 ] 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82