Разработка класса C ++ для отмены изменений другого члена класса - PullRequest
2 голосов
/ 19 марта 2012

У меня есть композиция предметов с обременительным дизайном.Классы X и Y являются схематическим представлением этого проекта, где Y является компонентом X.

class Y {
public:
    std::string _name;
    Y(std::string name) : _name(name) {}
};

class X {
    Y _y;
public:
    X(std::string name) : _y(name) {}
    Y getY() { return _y; }
    Y* getYPtr() { return &_y; }
};

Обратите внимание, что std::string _name в Y является общедоступным.

Что я хочудля этого нужно получить доступ к Y::_name через экземпляр X, записать для него новые значения и иметь возможность легко отменить операцию записи в других частях программы.

Моя попыткаследующим образом: я использую объект Undo, который содержит три информации:

  • Указатель на строку, для которой отменяется действие
  • Строка, содержащая старое имя
  • Строка, содержащая новое имя

.

class Undo {
    std::string _oldName;
    std::string _newName;
    std::string *_internalName;
public:
    Undo(std::string *name) : _internalName(name) {}
    void setOldName(std::string oldName) {
        _oldName = oldName;
    }
    void setNewName(std::string newName) { 
        _newName = newName;
    }
    void undoToOldName() {
        *_internalName = _oldName;
    }
};

Если я хочу отменить операцию записи, мне нужно только вызвать метод undoToOldName() в Undo object.

Пример:

X x("firstName");

Y *y = x.getYPtr();

// Prepare the undo object
Undo undo(&(y->_name));
undo.setOldName(y->_name);
undo.setNewName("secondName");

// Set new name
y->_name = "secondName";

// Output: secondName
std::cout << x.getY()._name << std::endl;

// Undo
undo.undoToOldName();

// Output: firstName
std::cout << x.getY()._name << std::endl;

Что мне не нравится в этом проекте, так это необходимость использования Y * getter.

В качестве ограничения,Я не могу изменить дизайн композиции.

Не могли бы вы предложить альтернативный дизайн?ГНС за это?

Спасибо.

Ответы [ 3 ]

1 голос
/ 19 марта 2012

Несколько комментариев: объект Undo не должен нуждаться в методе setOldName. Он может понять это, так как имеет строковый указатель. Во-вторых, ему тоже не нужно setNewName; ему просто нужен метод, чтобы сказать ему , когда установлено новое значение. (Предполагая, что вам это вообще нужно, в чем я сомневаюсь)

Хорошая настройка - getYPtr() вернуть undo_ptr<Y>. Это тонкая прокладка, которая знает о связанном Undo объекте. Когда вызывается undo_ptr<Y>::~undo_ptr, т. Е. Когда клиент готов, вызывается связанный с ним метод Undo::newNameSet. Как отмечено выше, это просто извлекает новое значение через предоставленный указатель.

Пример:

X x("firstName");
{  
  Undo undo(x, &X::name); // Slightly cleaner interface. Saves "firstName".
  Y* y = x.getYPtr();
  y->_name = "secondName";
  // Output: secondName
  std::cout << x.getY()._name << std::endl;
  // Undo (calls Undo::operator(), the convention for functors).
  undo();
  // Output: firstName
  std::cout << x.getY()._name << std::endl;
}

Как видите, в этом случае нет необходимости записывать новое имя, поэтому вам не нужна структура undo_ptr<Y>.

1 голос
/ 19 марта 2012

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

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

0 голосов
/ 19 марта 2012

Почему новый класс?Вы можете расширить класс X с помощью методов beginTransaction, commitTransaction и rollbackTransaction, а также с помощью метода update, который при желании будет подтвержден, если он вызывается вне транзакции.

...