Шаг 28 - Классы объектов, поддерживающие транзакции. Продолжение 2

Классы объектов, хранящие состояния, получились очень неплохие - при минимальных интеллектуальных затратах, хотя о транзакциях говорить рано: для транзакций они недостаточно кислотные. (ACID - Atomic, Consistent, Isolated, Durable). Не хватает вот чего:

  1. Объекты, задействованные в транзакции, блокируются на запись.
  2. Объекты, задействованные в транзакции, представляют другим клиентам свое состояние до транзакции.

Мы уже понимаем общий принцип: если нужна дополнительная логика - вынесите ее на отдельный уровень. Что означает это в нашем случае? То, что 1: транзакция должна быть представлена отдельным уровнем == отдельным классом; 2: объекты, задействованные в транзакции, должны поддерживать специальный стандартный интерфейс, за который транзакция должна ими рулить. То есть, они либо должны быть порождены от специального абстрактного базового класса, либо они должны быть упакованы в специальный смарт-указатель - делающий то же самое.

Все остальное - дело техники. Сразу поясняю код: класс CLockable (базовый) содержит указатель на транзакцию, к которой принадлежит в данный момент, а так же чистые виртуальные функции rollback и commit, назначение которых очевидно. Класс CTransaction представляет собой транзакцию, и содержит в себе список задействованных объектов - затем чтобы роллбачить или коммитить их все вместе, да чтоб на ходу можно проверить - принадлежит ли объект некоей транзакции или нет. Функция addLock() добавляет объект к транзакции, то есть распространяет свое действие на него, тем самым блокируя изменения со стороны других клиентов. После использования объекты автоматически разрегистрируются.

Хочу еще раз напомнить о принципе, который в этом Шаге формулируется первый раз: если существует определенная, законченная логика взаимодействия объектов или систем - она выносится на отдельный уровень. Поддержка транзакций очевидно является законченной бизнес-логикой, и, следовательно, должна быть вынесена на специальный уровень. Пусть классы реализуют свою функциональность, не заботясь о свопе, многозадачности-поточности, транзакциях, доступе, приоритетах, авторизациях, синхронизации, сборке мусора (garbage collection) или уплотнении памяти - это не его проблемы. Это - другие уровни, которыми занимаются другие классы, или даже системы. В современном компьютерном мире этот принцип сегодня доминирует, именно на него работают новомодные COM+ с MTS, IBM-websphera, JavaBeans, и множество иных. То, что грамотная корпоративная система должна работать в минимум четырех уровнях, уже является прописной истиной: данные, бизнес-логика сервера, бизнес-логика клиента, клиент.

Понимаете теперь, в чем преимущество смарт-указателей? Они позволяют с легкостью создавать новые уровни бизнес-логик без привлечения дополнительных средств и схем, а едиными только средствами языка. Попробуйте сделать что-либо подобное на языке, который не поддерживает указателей и перегрузки операторов (да шаблоны еще)! Вот код.

#include "ampstack.h"
// Абстрактный базовый класс
class CLockable {
friend class CTransaction;
protected:
	// текущая транзакция, если есть
 	CTransaction* m_trans;
public:
CLockable () : m_trans (NULL){};
// регистрируемся в какой-то транзакции
	int regObj (CTransaction* _pt);
	// и разрегистрируемся
	void unregObj ();
	virtual ~CLockable (){};
	virtual void rollback () =0;
	virtual void commit() =0;
};
// Класс транзакции
class CTransaction { 
friend class CLockable; 
private:
 // коллекция зарегистрированных объектов
	ampstack<CLockable> m_locks;
	// добавить объект к транзакции
	void addLock (CLockable*);
public:
	virtual ~CTransaction ();
	// закрепить или отменить все изменения во всех
	// зарегистрированных объектах.
	void commit();
	void rollback();
	// проверить, зарегистрирован ли объект в этой транзакции
	int allready_locked(CLockable*);
};
// зарегистрироваться в транзакции
inline int CLockable::regObj (CTransaction* _pt)
{
	if (m_trans != NULL)
		return 0;
	else
	{
		_pt->addLock (this);
		m_trans = _pt;
 		return 1;
	}
}
// разрегистрироваться
inline void CLockable::unregObj ()
{
	m_trans = NULL;
}
// добавление объекта к транзакции.
inline void CTransaction::addLock(CLockable* _lc) {
	// а именно, воткнуть указатель на него в стек.
	m_locks.push (_lc);
}
// закрепление всех объектов
void CTransaction::commit()
{
	//  создаем итератор
 	ampIter<CLockable> it(&(this->m_locks));

	// пробежались по всем, закрепились. 
	it.goStart();
	while (!it.isLast())
		it.moveNext()->commit();
	// Всех выкинуть из стека, разрегистрировать.
	while (!m_locks.isEmpty()) 
		m_locks.pop()->unregObj();
}
// отмена всех объектов
void CTransaction::rollback()
{
	// создали итератор
 	ampIter<CLockable> it(&(this->m_locks));
	// пробежались по всем, отменились.
	it.goStart();
	while (!it.isLast())
		it.moveNext()->rollback();
	// Всех выкинуть из коллекции и разрегистрировать
	while (!m_locks.isEmpty()) 
		m_locks.pop()->unregObj();
}
// проверка, зарегистрирован ли объект.
int CTransaction::allready_locked(CLockable* _lc)
{
	// создали итератор
 	ampIter<CLockable> it(&(this->m_locks));
	it.goStart();
	while (!it.isLast())
		if (it.moveNext() == _lc) 
			return 1; 
	return 0;
}

Предыдущий Шаг | Следующий Шаг | Оглавление
Автор Albert Makhmutov - 28.06.2001