强制保证对象状态不变性的通用解决方案或设计模式有哪些
通用解决方案可分为运行期校验和编译期保障两大类,按优先度排序如下:
1. 消除冗余字段,从根源避免同步问题
你当前类中存储的top/left/width/height和center_x/center_y两组数据存在冗余,是同步问题的核心来源。优先选择只存储一组基础数据,另一组通过计算动态返回即可:
class Rectangle { public: void setShape(float top, float left, float width, float height) { this->top = top; this->left = left; this->width = width; this->height = height; } void setCenter(float cx, float cy) { left = cx - width / 2; top = cy - height / 2; } // 中心点改为动态计算,不做存储 float getCenterX() const { return left + width / 2; } float getCenterY() const { return top + height / 2; } private: float top, left, width, height; };
这种方案没有任何额外开销,也完全不会出现状态不一致的问题,是绝大多数场景下的最优选择。
2. 冗余存储场景下的运行期校验
如果确实有必要缓存冗余字段(比如字段计算开销极高、读取频次远高于修改频次),可以通过两个手段保证状态一致:
- 收敛修改入口:把所有成员变量的赋值逻辑收敛到同一个私有方法中,所有对外的setter都只负责计算参数,最终调用同一个内部方法完成全量赋值,避免每个setter单独修改字段漏更的问题。
- 不变性断言校验:新增私有不变性检查方法,在所有public方法返回前调用,Debug模式下可以第一时间定位到不符合约定的状态修改:
class Rectangle { public: void setShape(float top, float left, float width, float height) { this->top = top; this->left = left; this->width = width; this->height = height; this->center_x = left + width / 2; this->center_y = top + height / 2; checkInvariant(); } void setCenter(float cx, float cy) { left = cx - width / 2; top = cy - height / 2; center_x = cx; center_y = cy; checkInvariant(); } private: float top, left, width, height, center_x, center_y; void checkInvariant() const { // Debug模式下状态不合法直接触发断言,加入浮点误差兼容逻辑 assert(abs(center_x - (left + width/2)) < 1e-6); assert(abs(center_y - (top + height/2)) < 1e-6); } };
3. 编译期强制保障的不可变类方案
如果你的业务场景中Rectangle不需要高频修改,可以设计为不可变类:所有成员变量用const修饰,没有setter方法,修改属性时直接返回新的实例:
class Rectangle { public: Rectangle(float top, float left, float width, float height): top(top), left(left), width(width), height(height), center_x(left + width / 2), center_y(top + height / 2) {} Rectangle withShape(float newTop, float newLeft, float newWidth, float newHeight) const { return Rectangle(newTop, newLeft, newWidth, newHeight); } Rectangle withCenter(float cx, float cy) const { return Rectangle(cy - height / 2, cx - width / 2, width, height); } // 所有getter保持不变 private: const float top, left, width, height, center_x, center_y; };
这种方案把状态一致性的保证完全放到了构造函数中,编译期就禁止了任何对已有对象状态的修改,从根本上杜绝了setter导致的状态不一致问题。
内容的提问来源于stack exchange,提问作者Yakov Dan
相关产品推荐
相关产品推荐

