【问题标题】:Design for a C++ class to undo modifications to another class member为 C++ 类设计以撤消对另一个类成员的修改
【发布时间】:2012-04-03 20:46:37
【问题描述】:

我有一个设计繁琐的对象组合。 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; }
};

注意Y 中的std::string _name 是公开的。

我想做的是通过X 的实例访问Y::_name,为其写入新值,并有可能在程序的其他部分轻松撤消写入操作。

我的尝试如下:我使用一个包含三个信息的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;
    }
};

如果我想撤消一个写操作,我只需要在Undo对象上调用undoToOldName()方法。

例子:

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。

作为一个约束,我不能改变构图的设计。

您能否为此推荐替代设计?

谢谢。

【问题讨论】:

  • 是否允许使用X::setY() 方法?如果是这样,它将允许删除 getYPtr() 并实现 X::rollbackY() 方法。
  • 也许让你的 Undo 类成为 Y 的朋友,所以它可以设置_y._name?如果您希望 Undo 记住要处理哪个 X 或 Y,它可以在构造函数中获取一个引用,并且 - 使用初始化列表 - 记录它以供以后使用。避免指针。坦率地说,虽然这有点乱...... :-(
  • @hmjd 感谢您的评论。不,这是不可能的,因为一个X 有许多类似于Y 的对象。其中只有一个是有效的,这由枚举确定。一旦我发现要编辑哪个Y,我就不想再通过枚举了。此外,一个Y 有许多_ys,并不是所有的_ys 都有资格撤消。我承认设计很丑,但这就是我拥有的图书馆:)。
  • @TonyDelroy 感谢您的建议,但我认为这行不通,因为 Y 中有许多 y_s。在问题中省略了这个细节是我的错。

标签: c++ design-patterns undo


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    一些 cmets:Undo 对象不需要 setOldName 方法。它可以弄清楚,因为它有字符串指针。其次,它也不需要setNewName;它只需要一个方法来告诉它何时设置了新值。 (假设你完全需要它,我对此表示怀疑)

    一个不错的设置是让getYPtr() 返回一个undo_ptr&lt;Y&gt;。这是一个薄垫片,它知道关联的Undo 对象。当undo_ptr&lt;Y&gt;::~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&lt;Y&gt; 的框架。

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-03-15
        • 1970-01-01
        相关资源
        最近更新 更多