151
// Изменившиеся определения
template
LockPtr::LockPtr(Transaction* t, ConstPtr* cp)
: transaction(t), master_ptr(cp),
new_image(cp->old_image != NULL ? new Type(*(cp->old_image)) : NULL)
{
}
template
void LockPtr::Commit()
{
delete
master_ptr->old_image;
master_ptr->old_image = new_image;
if (new_image != NULL)
new_image = new Type(*new_image);
}
template
Type* LockPtr::operator->() const
{
if (new_image == NULL)
//
исключение
return
new_image;
}
template
void LockPtr::Make()
{
delete
new_image;
//
Если new_image не равен NULL
new_image = new Type; // Новый пустой объект
}
template
void LockPtr::Destroy()
{
delete
new_image;
new_image = NULL;
}
Функция
Make()
соблюдает семантику присваивания, что позволяет вызвать ее для существующего
указываемого объекта. При этом объект, на который в данный момент ссылается
LockPtr
,
уничтожается и заменяется новым пустым объектом.
Упрощенное создание объектов
Объекты теперь создаются в три этапа:
1. Создать
ConstPtr
, указывающий на
NULL
.
2. Запросить у него
Lock
.
3. Потребовать у
Lock
создать объект функцией
Make()
.
Конечно, это произведет впечатление на ваших коллег и лишний раз докажет вашу техническую
квалификацию, но… они косо посмотрят на вас и вернутся к оператору
new
. Ведь он справляется с
задачей за один этап, а нас окружают занятые, очень занятые люди. Существует несколько способов
свести процесс создания к одному этапу, и самый простой из них — включить в
ConstPtr
другой
конструктор.
152
ConstPtr::ConstPtr(Transaction* t) : old_image(NULL), lock(NULL)
{
LockPtr& lp = Lock(t);
lp.Make();
}
Последующий вызов
Lock()
возвращает уже созданный
LockPtr
.
Отмена
Все до смешного просто. Если вы внимательно следили за тем, как развивается тема транзакций, то для
безопасной и универсальной реализации отмены вам потребуется совсем немного — включить
необходимые фрагменты из вашей любимой библиотеки классов с графическим интерфейсом
пользователя. Для создания и манипуляций со структурами данных транзакций идеально подходят
классы событий. Объект-событие создается для выполнения некоторой команды или внесения
изменений. Например, пользователь выбирает команду
Delete
в меню
Edit
или нажимает клавишу
Delete
на клавиатуре. Объект создается на базе класса, который прекрасно разбирается в удалениях. В
дальнейшем этот объект не только вносит затребованные изменения, но и обеспечивает работу
команды
Undo
меню
Edit
. Создайте транзакцию в его конструкторе, воспользуйтесь транзакцией для
сохранения всех изменений, а затем — для поддержки отмены.
Варианты
Мы не будем пережовывать дополнительный код, относящийся к транзакциям. Если вы еще не уловили
общий принцип, бессмысленно подливать масло в огонь, а если уловили, то без труда реализуете все, о
чем говорится ниже. Так что я ограничусь лишь замечаниями, в которых описываются некоторые
важные вариации на данную тему.
Вложенные блокировки
Чтобы вся методика имела хоть какой-то смысл, мы должны спрятать ключи от указываемых объектов
и заставить всех работать с
ContrPtr
и
LockPtr
. Это относится не только к изменениям, вносимым в
указываемые объекты непосредственно клиентами, но и к тем, которые один указываемый объект
вносит в другой. Таким образом, в общем случае указываемые объекты должны обращаться друг к
другу через
ConstPtr
и
LockPtr
, как и клиенты.
class A {
private:
ConstPtr& b;
// ConstPtr, как выше
//
И т.д.
};
Если экземпляр класса
A
захочет вызвать неконстантную функцию
b
, он должен сначала получить
LockPtr
для
b
. Таким образом, класс
A
должен знать, какую транзакцию он выполняет при попытке
изменения
b
, и эта информация должна поступить от клиента. Для простоты назовем объекты, к
которым клиенты обращаются напрямую, первичными, а объекты, косвенно обновляемые указываемым
объектом, — вторичными по отношению к указываемому объекту (один и тот же объект может
одновременно быть и первичным, и вторичным).
Если вторичный объект инкапсулируется в первичном (то есть вторичный объект невидим для
окружающего мира), это правило можно обойти. Конструктор копий первичного объекта должен
продублировать вторичный объект, а оператор
=
первичного объекта должен продублировать
вторичный объект правостороннего выражения. При соблюдении этих условий логика создания
образов в
ConstPtr
и
LockPtr
будет правильно работать с инкапсулированными объектами. Это
происходит автоматически в тех случаях, когда вторичный объект внедряется в первичный (то есть
когда в каждом экземпляре A объект
B
хранится не как указатель, а как обычная переменная класса):
class A {
private:
Do'stlaringiz bilan baham: |