私有方法何时应采用公共路线访问私有数据?


11

私有方法何时应采用公共路线访问私有数据?例如,如果我有这个不可变的“乘数”类(我知道有点作弊):

class Multiplier {
public:
    Multiplier(int a, int b) : a(a), b(b) { }
    int getA() const { return a; }
    int getB() const { return b; }
    int getProduct() const { /* ??? */ }
private:
    int a, b;
};

我可以通过两种方式实现getProduct

    int getProduct() const { return a * b; }

要么

    int getProduct() const { return getA() * getB(); }

因为这里的意图是使用的值(aget agetA()来实现getProduct()对我来说更干净。我宁愿避免使用,a除非我必须对其进行修改。我担心的是,我通常不会看到这样编写的代码,以我的经验,a * b这将是比更为常见的实现getA() * getB()

私有方法可以直接访问某些东西时是否应该使用公共方法?

Answers:


7

这取决于的实际意义abgetProduct

吸气剂的目的是能够更改实际实现,同时保持对象的接口相同。例如,如果某天getA变为return a + 1;,则更改将本地化到一个吸气剂。

实际情况下的案例有时比通过与getter关联的构造函数分配的常量后备字段更为复杂。例如,可以在代码的原始版本中从数据库计算或加载该字段的值。在下一版本中,可能会添加缓存以优化性能。如果getProduct继续使用计算的版本,则无法从缓存中受益(否则维护者将进行两次相同的更改)。

如果它使完美的意义getProduct使用ab直接使用它们。否则,请使用吸气剂防止以后出现维护问题。

一个使用吸气剂的例子:

class Product {
public:
    Product(ProductId id) : {
        price = Money.fromCents(
            data.findProductById(id).price,
            environment.currentCurrency
        )
    }

    Money getPrice() {
        return price;
    }

    Money getPriceWithRebate() {
        return getPrice().applyRebate(rebate); // ← Using a getter instead of a field.
    }
private:
    Money price;
}

尽管目前,getter不包含任何业务逻辑,但不排除构造函数中的逻辑将迁移到getter,以避免在初始化对象时进行数据库工作:

class Product {
public:
    Product(ProductId id) : id(id) { }

    Money getPrice() {
        return Money.fromCents(
            data.findProductById(id).price,
            environment.currentCurrency
        )
    }

    Money getPriceWithRebate() {
        return getPrice().applyRebate(rebate);
    }
private:
    const ProductId id;
}

以后,可以添加缓存(在C#中,将使用Lazy<T>,使代码简短易行;我不知道C ++中是否存在等效项):

class Product {
public:
    Product(ProductId id) : id(id) { }

    Money getPrice() {
        if (priceCache == NULL) {
            priceCache = Money.fromCents(
                data.findProductById(id).price,
                environment.currentCurrency
            )

        return priceCache;
    }

    Money getPriceWithRebate() {
        return getPrice().applyRebate(rebate);
    }
private:
    const ProductId id;
    Money priceCache;
}

两项更改都集中在getter和backing字段上,其余代码不受影响。相反,如果我在中使用了字段而不是getter getPriceWithRebate,那么我也必须反映那里的更改。

可能使用私有字段的示例:

class Product {
public:
    Product(ProductId id) : id(id) { }
    ProductId getId() const { return id; }
    Money getPrice() {
        return Money.fromCents(
            data.findProductById(id).price, // ← Accessing `id` directly.
            environment.currentCurrency
        )
    }
private:
    const ProductId id;
}

该getter很简单:它是常量readonly字段的直接表示形式(类似于C#的字段),预计该字段将来不会更改:机会是,ID getter永远不会成为计算值。因此,请保持简单,并直接访问该字段。

另一个好处是,getId如果看起来好像没有在外部使用,则将来可能会删除它们(就像在上一段代码中一样)。


我不能给您+1,因为您使用私有字段的示例不是一个恕我直言,主要是因为您已经声明了const:我认为这意味着编译器getId无论如何都会内联调用,并且允许您在任一方向进行更改。(否则,我完全同意你的理由使用干将。),并且在那些提供物业语法的语言,还有的甚至更少理由不使用属性,而不是直接支持字段。
Mark Hurd

1

通常,您将直接使用变量。您希望在更改类的实现时更改所有成员。不直接使用变量只会使正确隔离依赖于它们的代码变得更加困难,并且使读取成员变得更加困难。

如果吸气剂实现了真正的逻辑,这当然是不同的,在这种情况下,这取决于您是否需要利用它们的逻辑。


1

我要说的是,使用公共方法将是可取的,如果不是出于任何其他原因,而是遵循DRY的话

我知道在您的情况下,您为访问器提供了简单的后备字段,但是您可能具有某些逻辑,例如延迟加载代码,您需要在首次使用该变量之前运行该逻辑。因此,您希望调用访问器,而不是直接引用您的字段。即使在这种情况下您没有这个,也要遵守一个约定。这样,如果您更改了逻辑,则只需在一个地方进行更改。


0

对于一堂课来说,如此简单,简单便会取胜。我只用* b。

对于更复杂的事情,如果我想清楚地将“最小”接口与完整公共API中的所有其他函数分开,我将强烈考虑使用getA()* getB()。一个很好的例子是C ++中的std :: string。它具有103个成员函数,但实际上只有32个成员需要访问私有成员。如果您有一个复杂的类,则强制所有“非核心”功能一致地通过“核心API”可能会使实现更易于测试,调试和重构。


1
如果您的课程如此复杂,则应强迫您对其进行修复,而不是对其进行创可贴。
DeadMG

同意 我可能应该选择一个只有20-30个函数的示例。
Ixrec 2015年

1
“ 103个功能”有点让人讨厌。就接口复杂度而言,重载方法应计算一次。
Avner Shahar-Kashtan

我完全不同意。不同的重载可以具有不同的语义和不同的接口。
DeadMG

即使从这个“小”例子getA() * getB()来看,从中长期来看还是更好的。
Mark Hurd
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.