Форум программистов, компьютерный форум, киберфорум
C++
Войти
Регистрация
Восстановить пароль
Карта форума Темы раздела Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.50/4: Рейтинг темы: голосов - 4, средняя оценка - 4.50
274 / 175 / 12
Регистрация: 14.03.2010
Сообщений: 501
1

Откатываемый (undoable) класс

16.03.2010, 22:07. Показов 800. Ответов 6
Метки нет (Все метки)

Author24 — интернет-сервис помощи студентам
Решил тут сделать универсальный интерфейс "откатываемости", который можно было бы прикрутить к любому классу. Идея в том, чтобы, унаследовав свой класс от некоторого класса Undoable, получить возможность сохранять, отменять и повторять изменения, произошедшие с классом. Ну, как undo/redo в любой программе.
Но смысл именно в универсальности, т.е. отменять действия не на уровне приложения, а на уровне каждого отдельного класса, которому такая функциональность необходима.

Первый вариант я сделал. Но результат пока что не тот, который хотелось бы видеть. Функциональность я реализовал, но для её получения мне необходимо наследование "наоборот", т.е. унаследовать не мой класс от класса Undoable, а Undoable от моего класса.
Это решение по-своему хорошо, но мне всё-таки хотелось поместить Undoable на вершину иерархии. Не подкинете идейку?

Исходный код прилагается.

Undoable.h

C++
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
/* Данный файл содержит описание класса Undoable, расширения, дающего возможность
любому классу запоминать и восстанавливать свои состояния. */
 
#include <vector>
#import "vectorServices.h"
 
template <class T>
class Undoable : public T {
    typedef std::vector<T *> TPointerArray;
    typedef typename TPointerArray::iterator TPointerArrayIterator;
 
public:
    Undoable () {}
    ~Undoable () { deleteVector(versions); }
    
    /* Сохраняет текущее состояние объекта. Если итератор "currentVersion"
    установлен не на конец массива копий состояний, то все последующие
    сохранённые состояния удаляются и в конец добавляется новое
    сохранённое состояние. */
    void save () {
        T * newObject = new T(*this);
    
        if (!versions.empty()) versions.erase(currentVersion + 1, versions.end());
        versions.push_back(newObject);
        currentVersion = versions.end() - 1;
    }
    /* Пытается откатить состояние объекта к предыдущему сохранённому.
    Если объект находится в своём начальном состоянии, т.е. в первом из
    сохранённых, то никаких изменений не производится и возвращается
    "false". В противном случае объект трансформируется в своё предыдущее
    сохранённое состояние, и возвращается "true". */
    bool undo () {
        if (currentVersion > versions.begin()) {
            T::operator=(**--currentVersion);
            return true;
        } else return false;
    }
    /* Пытается вернуть состояние объекта к следующему сохранённому.
    Если текущее сохранённое состояние является последним из сохранённых,
    то никаких изменений не производится и возвращается "false". В противном
    случае объект трансформируется в своё следующее сохранённое состояние,
    и возвращается "true". */
    bool redo () {
        if (currentVersion < versions.end() - 1) {
            T::operator=(**++currentVersion);
            return true;
        } else return false;
    }
    /* Удаляет все сохранённые состояния, кроме текущего. */
    void clearUndoHistory () {
        if (!versions.empty()) {
            currentVersion = versions.erase(versions.begin(), currentVersion);
            versions.erase(currentVersion + 1, versions.end());
            currentVersion = versions.begin();
        }
    }
 
private:
    TPointerArray versions;
    TPointerArrayIterator currentVersion;
};


Пример использования

C++
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
#import "Undoable.h"
 
// UVector (undoable vector) — расширение стандартного вектора.
template <class T>
class UVector : public Undoable<std::vector<T> > {};
 
int main (int argc, char * argv[]) {
    UVector<int> v;
    
    randomVector(v, 10);
    showVector(v);
    v.save();
    
    v.erase(v.begin() + 3, v.end());
    showVector(v);
    v.save();
    
    v.undo();
    showVector(v);
    
    v.redo();
    showVector(v);
    
    return 0;
}
Результат выполнения:

3 6 7 5 3 5 6 2 9 1
3 6 7
3 6 7 5 3 5 6 2 9 1
3 6 7
1
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
16.03.2010, 22:07
Ответы с готовыми решениями:

Есть класс A и класс B, класс B вложен в класс A и вложен в него, как классу B получить доступ к переменным класса A просто по имени?
На самом деле ничё фантастического я не прошу, ведь: template &lt;class T&gt; class matrix { ...

Класс таблиц баз данных и класс записей в таблице(реляционная таблица). Предусмотреть класс связей между таблицами
Здравствуйте! Никак не могу продумать структуру этой программы. Проходим наследование, но я все...

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

Отдельно класс данных, класс формул и главный класс
Здравствуйте! У меня есть три вопроса. Первый вопрос. Скажите пожалуйста! Правильный ли это...

6
Эксперт С++
1674 / 1046 / 174
Регистрация: 27.09.2009
Сообщений: 1,945
17.03.2010, 20:17 2
Идейка называется CRTP (Curiously Recurring Template Pattern). Вкратце выглядит так:
C++
1
2
3
4
5
6
7
8
9
template<typename T>
class Undoable {
// все операции настраиваем на работу с типом T
};
 
class MyClass: public Undoable<MyClass>
{
// независимая реализация MyClass, к которой автоматом добавляется функциональность Undoable.
};
0
274 / 175 / 12
Регистрация: 14.03.2010
Сообщений: 501
18.03.2010, 13:22  [ТС] 3
Nick Alte, что-то пока не получается реализовать. Ты не мог бы схематично расписать одну из функций класса Undoable?
В моём способе Undoable наследуется от класса T, поэтому он может работать с переменными этого класса. А как работать с классом, когда ты не знаешь, что в нём лежит?

Добавлено через 31 минуту
А, я понял, там нужно приведение типов делать. Но всё равно суть остаётся та же: откатываемый класс находится на дне иерархии. А вот можно ли как-нибудь его наверх вытащить?

Добавлено через 15 часов 21 минуту
Так-с, с паттерном, вроде, разобрался. Спасибо за наводку.
Но по-прежнему не могу создать копию объекта внутри класса Undoable.

Вот такая конструкция в классе Undoable
C++
1
2
3
void save () {
    T * newVersion = new T(*static_cast<T *>(this));
}
выдаёт ошибку:

/Developer/SDKs/MacOSX10.5.sdk/usr/include/c++/4.0.0/debug/safe_iterator.h:127:
error: attempt to copy-construct an iterator from a singular iterator.

Objects involved in the operation:
iterator "this" @ 0x0x1001cc {
type = N11__gnu_debug14_Safe_iteratorIN9__gnu_cxx17__normal_iteratorIPP7myClassN10__gnu _norm6vectorIS4_SaIS4_EEEEEN15__gnu_debug_def6vectorIS4_S8_EEEE (mutable iterator);
state = singular;
}
iterator "other" @ 0x0xbffff724 {
type = N11__gnu_debug14_Safe_iteratorIN9__gnu_cxx17__normal_iteratorIPP7myClassN10__gnu _norm6vectorIS4_SaIS4_EEEEEN15__gnu_debug_def6vectorIS4_S8_EEEE (mutable iterator);
state = singular;
}


Как решить проблему?
0
Эксперт С++
1674 / 1046 / 174
Регистрация: 27.09.2009
Сообщений: 1,945
18.03.2010, 20:52 4
Тут идёт какая-то заморочка с итераторами. Для обычных типов конструкция правильная, должно работать.
0
Эксперт С++
2347 / 1720 / 148
Регистрация: 06.03.2009
Сообщений: 3,675
18.03.2010, 21:04 5
volovzi, то что ты пытаешься сделать невыгодно.

Предположим у меня есть объект имеющий 1000 членов данных. Я перед выполнением некой операции, изменяющей 1 член данных, сохраняю состояние всех членов с помощью вызова твоей операции save (вызывая возможно очень дорогостоящий конструктор копирования).
Вопрос на кой хрен мне хранить лишние состояние 999 членов, если я изменения касаются только одного?

volovzi, все уже придумано до тебя, см. в сторону паттерна COMMAND (Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидес Приемы объектно-ориентированного проектирования. Паттерны проектирования).
1
1261 / 799 / 108
Регистрация: 16.09.2009
Сообщений: 2,010
18.03.2010, 21:10 6
Начал эту книгу читать, книга ok...
0
274 / 175 / 12
Регистрация: 14.03.2010
Сообщений: 501
19.03.2010, 01:56  [ТС] 7
Цитата Сообщение от CyBOSSeR Посмотреть сообщение
volovzi, то что ты пытаешься сделать невыгодно.

Предположим у меня есть объект имеющий 1000 членов данных. Я перед выполнением некой операции, изменяющей 1 член данных, сохраняю состояние всех членов с помощью вызова твоей операции save (вызывая возможно очень дорогостоящий конструктор копирования).
Вопрос на кой хрен мне хранить лишние состояние 999 членов, если я изменения касаются только одного?
Действительно... Но надо же с чего-то начинать :) .

volovzi, все уже придумано до тебя, см. в сторону паттерна COMMAND (Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидес Приемы объектно-ориентированного проектирования. Паттерны проектирования).
Спасибо, прочту.
0
19.03.2010, 01:56
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
19.03.2010, 01:56
Помогаю со студенческими работами здесь

Реализовать класс родительский класс "Пара чисел" и класс-наследник "Комплексное число"
Ткма работы создать класс pair (пара чисел); определить метод перемножения полей и операцию...

Создать класс колесо, имеющее радиус. Определить конструкторы. Создать класс машин, содержащий класс колесо
Создать класс колесо, имеющее радиус. Определить конструкторы. Создать класс машин, содержащий...

Класс, производный класс, паблик-класс
Задание: Создать класс колесо, которое имеет радиус. Определить конструкторы и метод доступа....

Разработать базовый класс "прямоугольник", производный класс "Окно" и класс-контейнер "Список окон"
Реализовать все задачи в С # WPF 1. Разработать класс прямоугольник, имеющий координаты верхнего...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
7
Ответ Создать тему
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru