プライベートメソッドがプライベートデータにアクセスするためにパブリックルートを使用する必要があるのはいつですか?


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;
};

実装できる方法は2つありますgetProduct

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

または

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

ここでの意図は、の値を使用することa、つまりget aを使用getA()することであるため、実装に使用することgetProduct()は私にとってはよりクリーンに思えます。a変更する必要がない限り、使用を避けたいと思います。私の懸念は、コードがこのように書かれていることはあまりないということです。私の経験でa * bは、より一般的な実装になりgetA() * getB()ます。

プライベートメソッドが何かに直接アクセスできる場合、パブリックメソッドを使用する必要がありますか?

回答:


7

これは、実際の意味に依存abgetProduct

ゲッターの目的は、オブジェクトのインターフェースを同じに保ちながら、実際の実装を変更できるようにすることです。たとえば、ある日がにgetAなったreturn a + 1;場合、変更はゲッターにローカライズされます。

実際のシナリオのケースは、ゲッターに関連付けられたコンストラクターを通じて割り当てられる定数のバッキングフィールドよりも複雑な場合があります。たとえば、フィールドの値は、コードの元のバージョンのデータベースから計算またはロードできます。次のバージョンでは、パフォーマンスを最適化するためにキャッシュが追加される可能性があります。getProduct計算されたバージョンを引き続き使用する場合、キャッシングのメリットはありません(または、メンテナーが同じ変更を2回行います)。

それはのための完璧な感覚になり場合はgetProduct使用をaしてb、直接、それらを使用しています。それ以外の場合は、ゲッターを使用して、後でメンテナンスの問題を防ぎます。

ゲッターを使用する例:

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;
}

現時点では、ゲッターにはビジネスロジックは含まれていませんが、オブジェクトの初期化時にデータベースの作業を回避するために、コンストラクターのロジックがゲッターに移行されることは除外されません。

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;
}

どちらの変更もゲッターとバッキングフィールドに焦点を合わせており、残りのコードは影響を受けていません。代わりに、でゲッターの代わりにフィールドを使用していた場合は、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;
}

ゲッターは簡単です。これはreadonly、将来変更されることのない定数(C#に類似)フィールドの直接表現です。チャンスは、IDゲッターが計算値になることはありません。そのため、シンプルにして、フィールドに直接アクセスします。

もう1つの利点は、getId(前のコードのように)外部で使用されていないようであれば、将来削除される可能性があることです。


私があなたにプライベートフィールドを使用する例は私見ではないので、私はあなたに+1を与えることができません。主にあなたが宣言しているためですconst:私はそれはコンパイラがgetIdとにかく呼び出しをインライン化し、それがどちらの方向にも変更を加えることができることを意味すると仮定します。(そうでない場合、ゲッター使用する理由に完全に同意します。)プロパティ構文を提供する言語では、バッキングフィールドではなくプロパティを直接使用しない理由はさらに少なくなります。
マークハード

1

通常、変数を直接使用します。クラスの実装を変更するときは、すべてのメンバーを変更する必要があります。変数を直接使用しないと、それらに依存するコードを正しく分離することが難しくなり、メンバーの読み取りが難しくなります。

もちろん、ゲッターが実際のロジックを実装する場合は異なります。その場合、ロジックを利用する必要があるかどうかによって異なります。


1

他の理由ではなく、DRYに準拠する場合は、パブリックメソッドを使用することをお勧めします。

あなたの場合、アクセサの単純なバッキングフィールドがあることを知っていますが、その変数を初めて使用する前に実行する必要がある遅延ロードコードなどの特定のロジックを使用できます。したがって、フィールドを直接参照するのではなく、アクセサーを呼び出す必要があります。この場合、これはありませんが、単一の規則に従うことは理にかなっています。そうすれば、ロジックに変更を加えた場合でも、1か所で変更するだけで済みます。


0

クラスでは、この小さなシンプルさが勝ちます。a * bを使用します。

もっと複雑なものについては、完全なパブリックAPIの他のすべての関数から「最小」インターフェースを明確に分離したい場合は、getA()* getB()の使用を強く検討します。優れた例は、C ++のstd :: stringです。メンバー関数は103個ありますが、プライベートメンバーにアクセスする必要があるのはそのうち32個だけです。複雑なクラスがある場合、すべての「非コア」関数に一貫して「コアAPI」を通過させると、実装のテスト、デバッグ、およびリファクタリングがはるかに容易になります。


1
クラスが複雑な場合は、バンドエイドではなく、修正する必要があります。
DeadMG

同意した。たぶん、20〜30個の関数を持つサンプルを選択する必要がありました。
Ixrec

1
「103関数」は少し赤いニシンです。インターフェースの複雑さの観点から、オーバーロードされたメソッドは1回カウントする必要があります。
アヴナーシャハルカシュタン

私はまったく同意しません。異なるオーバーロードは、異なるセマンティクスと異なるインターフェースを持つことができます。
DeadMG

この「小さな」例でさえgetA() * getB()、中長期的には優れています。
マークハルド
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.