设计C ++类以撤消对另一个类成员的修改

时间:2012-03-19 11:00:15

标签: c++ design-patterns undo

我有一个设计繁琐的物体组合。 类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对象:

  • 指向撤消生效的字符串的指针
  • 包含oldname
  • 的字符串
  • 包含新名称
  • 的字符串

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方法。

示例:

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 *吸气剂。

作为约束,我无法改变构图的设计。

您能为此建议替代设计吗?

感谢。

3 个答案:

答案 0 :(得分:1)

一些评论: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 :(得分:1)

使用对象修饰符保护修订版的不变性非常棘手。如果您需要一次回滚多个字段而一个失败,您会怎么做?您的修订控件实际上可能会影响对象的正常操作。

版本对象的一种简单方法是保留对象的所有版本。当您需要回滚时,只需将其替换为旧版本的副本即可。

答案 2 :(得分:0)

为什么要上新课?您可以使用XbeginTransactioncommitTransaction方法扩展类rollbackTransaction,以及update方法,如果在事务之外调用它,则可以选择断言。