如何解决同时设置成员的 getter 问题?

How to work around a getter that also sets a member?

提问人:Luchian Grigore 提问时间:2/23/2013 更新时间:2/24/2013 访问量:354

问:

我正在处理以下几点:

我有一个非常复杂的类,并且该成员依赖于一些在初始化类时未设置或在旅途中设置的内容。即使未设置该成员,该类的对象也有意义。还可以根据对其他成员所做的其他更改来重置它。

现在,假设这个“特殊”成员的设置计算成本很高,因此我根据请求延迟计算它。

所以:

class Class
{
    X x;
    Y y;
    SpecialClass specialObject;
public:
    void setX(const X& newX);
    void setY(const Y& newY);

    //----
    SpecialClass getSpecialObject() /*const*/
    { 
        computeSpecialObject();
        return specialObject();
    }
private:
    void computeSpecialObject()
    {
        //specialObject depends on x and y
        //and is expensive to compute
        //this method is a bottleneck
    }
};

我不想在每次更改时都调用该方法,或者因为它很昂贵,所以我陷入了两难境地:computexy

  • 删除 ?从逻辑上讲,getter 应该是 ,但不能。还有一个缺点是不能在对象上调用它。constconstconst
  • 我可以做,但这似乎不是正确的做法。specialObjectmutable
  • 抛弃恒常?再次,看起来很腥。
  • 在获取之前打电话?- 如果有人忘记了怎么办?他们会得到一个过时的结果。computeSpecialObject

有没有处理这个问题的设计模式?一个好的方法?还是班级设计错了?(我倾向于最后一个,但改变班级并不是一个真正的选择)

注意:我已经制作了成员,想知道是否有更好的解决方案。mutable

C++ 设计模式 常量 getter

评论

1赞 Chad 2/23/2013
如果在调用 get 时总是重新计算特殊对象,那么何时实际使用缓存值?你能只返回一个新共同构造的对象并一起删除成员变量吗?

答:

3赞 Nik Bougalis 2/23/2013 #1

我会留下 并制作或保留指向 的指针,而不仅仅是将其“嵌入”到类中。constspecialObjectmutablespecialObject

我还会添加一个标志,并在进行使计算无效的更改时设置它。然后我会检查里面的标志,只有在设置好后才做工作。使用指针,每当更改使现有计算失效时,您甚至可以使用旧的计算对象,但这会打开一整套蠕虫。bool dirtymutablecomputeSpecialObjectdelete

还是我错过了什么?

4赞 Andy Prowl 2/23/2013 #2

我可以制作 specialObject ,但这似乎不是正确的做法。mutable

为什么会这样?这正是存在的原因:允许函数在逻辑上,而不需要物理上保持对象不变(如果你创建对象,请记住确保线程安全 - 我相信你明白我的意思)。mutableconstconstmutable

当然,只要对象的初始化不会改变对象的逻辑状态,这是正确的,因为这是承诺不会做的事情。SpecialClassconst

在这种情况下,函数本身根本不是本质上的,它可能应该被命名为不同的名称,而不仅仅是:可以是一个候选者。constgetSpecialObject()computeAndReturnSpecialObject()

评论

0赞 Luchian Grigore 2/23/2013
是的,我知道,似乎很合理,我只是觉得添加:(很脏mutable
2赞 Andy Prowl 2/23/2013
@LuchianGrigore:也许如果它感觉很脏,那是因为它以某种方式改变了对象的逻辑状态。是否有任何函数在初始化时其逻辑行为会发生变化?比如,它们在初始化而不是未初始化时调用时返回不同的东西?specialObjectspecialObject
1赞 Nik Bougalis 2/23/2013
@Luchian感觉有点像黑客,但如果你从更高层次的角度来看它,应该很清楚这是正确的事情。该函数不会更改对象本身,即使它确实进行了更改。它只是恢复/设置一些不变量。mutable
0赞 Luchian Grigore 2/23/2013
是的,其他东西也依赖于它,但它们使用 getter 而不是对象。
3赞 David Rodríguez - dribeas 2/23/2013
@LuchianGrigore:就是为此特殊目的而设计的。它允许您维护方法的签名,并确保只能修改可变状态。此外,请考虑添加一个脏标志来标识 和 值是否已更改(也),并且仅在该标志为脏时重新计算该值。mutableconstxymutable
0赞 Tony Hopkinson 2/23/2013 #3

这始终是一条细线,在不需要时不调用它,而不是在调用器中引入漏洞,这意味着它可能没有并返回不正确的结果。

我会将计算移动到特殊对象的方法,并将此类视为该类的计算方法参数的包装器。一个奖励球是你可以对计算进行单元测试。

然后,只需决定何时需要再次调用 SpecialObject.Compute(x,y) 或仅返回最后一个结果。 如果可以的话,我可能会考虑的另一件事是,如果 X 发生了变化,但 Y 没有,我是否可以简化计算。即保留一些中间结果。

不确定它对你有多适用,但我经常做的一件事就是注入一个执行计算的东西,所以我倾向于默认陷入这种模式。

0赞 Peter Wood 2/23/2013 #4

你可以去两个方向,或多或少.一个涉及的不是状态操纵,而是行为,另一个完全忘记了行为,而关心返回的状态。OOPFunctional

哎呀

对我来说,一个关键原则是 或 。OOPTell, Don't Askwrite no getters or setters

将你的对象设计成被告知该做什么,成为自主的。不要要求它返回一些对象,然后你可以用它来做某事。只要告诉它做你首先想做的事情。如果你告诉一个对象做某事,那么你可能会期望它改变状态,而它不适合。const

您可以提供一些服务。您可以改为告诉 ,这是正确的可变性。SpecialClassdoService()ClassdoSpecialService()

另一种方法是创建此对象,以使用其他对象进行创建。因此,函数可以是 const,但采用非 const 参数:

class Class {
public:
    void doService(ServiceProvider& serviceProvider) const {
        serviceProvider.doService(x, y);
    }
};

有了这个,您将传入一个 which 将为给定的 和 创建正确的 。它将是可变的。在提供服务时修改状态似乎是正确的。也许你可以有一个地图缓存 (, ) 对的对象。SpecialServiceProvider&SpecialClassXYSpecialClassXY

功能的

另一个方向是使对象不可变。每当您需要一些新状态时,请使用旧状态作为基础创建它。这可能会产生连锁反应,直到你(几乎)一直有海龟:

class SpecialBuilder {
public:
    SpecialBuilder withX(const X& newX) const;
    SpecialBuilder withY(const Y& newY) const;
    SpecialClass build() const;
};

SpecialBuilder specialBuilder;
SpecialClass special = specialBuilder.withX(x).withY(y).build();

您可以在每个返回的数据之间共享数据,因为它是不可变的。SpecialBuilder