タイプCar
のオブジェクトを含むクラスWheel
があるとします。
class Wheel {
public:
void SetFriction(double f) {
friction = f;
}
private:
double friction;
};
class Car {
private:
std::array<Wheel, 4> _wheels;
}
今、私は車の所有者にホイールの摩擦へのアクセスを提供したいと思います。これを行うには、主に2つの方法があります。
ホイールへのアクセスを許可します。
class Car {
Wheel& GetWheel(int id) {
return _wheels[id];
}
private:
std::array<Wheel, 4> _wheels;
}
Car beetle;
beetle.GetWheel(1).SetFriction(0.4);
摩擦に直接アクセスできるようにします。
class Car {
void SetWheelFriction(int id, double friction) {
_wheels[id].SetFriction(friction);
}
private:
std::array<Wheel, 4> _wheels;
}
Car beetle;
beetle.SetWheelFriction(1, 0.4);
最初のバージョンの問題は、プライベートメンバーへのアクセスを可能にし、カプセル化を壊す可能性があることです。たぶん、車の所有者に公開されるべきではない関数がホイールに追加されます(「SetManufacturer」など)。
一方、2番目のバージョンでは、Carで定義されている多くの関数が直接の責任ではないため、単一責任の原則に違反しています。
class Car {
void SetWheelFriction(int id, double friction) {
_wheels[id].SetFriction(friction);
}
double GetEngineWeight() {
return _engine.GetWeight();
}
double SetWindowsColor(Color& c) {
for(auto& w : _windows){
w.SetColor(c);
}
}
// And so on...
私は最初の選択肢に強く傾いていますが、他の誰かの意見を聞きたいです。
多分多少関連している: https://en.wikipedia.org/wiki/Entity%E2%80%93component%E2%80%93system
Car
クラスの主な責任が何であるかについては触れません。
実際のアプリケーションでは、Car
クラスは実際の自動車を表すだけではありません。特定のコンテキストでそれを表すでしょう。また、コンテキストを知ることは、クラスのパブリックAPIを形成するものであるため、重要です。
たとえば、このコンテキストがシミュレーションを実行して、レースカーのパフォーマンスを最適化するとします。 Car
クラスのパブリックAPIは、データとメソッドを車のTweakパフォーマンス特性に公開する可能性があります。このコンテキストでは、Simulation
クラスまたはPerformanceEvolver
クラスがCar
のAPIを使用してホイールの摩擦を直接調整することは非常に合理的です(例:car.setWheelFriction(WheelsEnum.FrontLeft, coefficient: 0.8)
。
ここで別のコンテキストを見てみましょう。
Car
クラスが部品表のコンテキストで使用されているとしましょう。ここでのCar
の主な責任は、ビルドに必要なコンポーネントを説明することです。確かに4つのホイールへの参照がありますが、これはCar
パブリックAPIに含まれるホイール中心のデータと動作の範囲です。
したがって、車のフロント左ホイールを作成するために必要なコンポーネントに興味がある場合、car.getFrontLeftWheelBillOfMaterials()
のようなことは期待できません。これは、wheelcarの懸念ではなく、懸念。代わりに、car.Wheels[WheelsEnum.FrontLeft].GetBillOfMaterials()
のようなことを行います。
だから、これを言うことすべて:
ハードで速い一般的なルールを探しているなら、あなたは運が悪いと思います。
ヘルプが必要な具体的な例がある場合は、その情報を質問に追加することをお勧めします。
メリークリスマス!
単一の責任原則(SRP)
SRPはクラスによって提供される機能についてではなく、 変更する理由について :
Wheel
を変更しても、Car
を変更する必要はありません。Wheel
インターフェースの変更には、Car
インターフェースの変更も必要になる場合があります。オプション1の問題
オプション1の主な問題は、オブジェクトへの参照を返すことです。この参照は、Car
の一部のユーザーが保存することができ(たとえば、ポインターでアドレスを取得)、Wheel
がその間に置き換えられた場合でも、後で使用できます。
より安全ですがより重いアプローチは、Wheel
インターフェースを公開する proxy オブジェクトを返すことですが、Car
を介してホイールにアクセスすることを保証します。
すべてのアプローチに隠された問題
あなたのアプローチの問題は、ホイールをカプセル化しようとする試みにもかかわらず、車には0から3の番号が付けられた4つのホイールがあると想定していることです。
それにもかかわらず、3つの車輪を備えたいくつかの小さな未来的な車を想像することができます。そして今日では、すでに6輪の大きなリムジンがいくつかあります。
したがって、少なくともホイールの数を示す関数を提供する必要があります。あるいは、事前定義された順序に従ってホイール間を反復できるようにする iterator を提供します。
2番目のアプローチを好む1つの理由は、デメテルの法則に準拠することです。最初のアプローチを選択した場合、クラス間の結合を増加させるこれらの長い呼び出しに終わる可能性があります。たとえば、コードはregion.getDealership().getBestSellingCar().getWheel().setFriction(0.25)
のようになりますが、デメテルの法則に準拠している場合はregion.setWheelFrictionOnBestSellingCar(0.25)
のようになります。
前者の場合、クラスはDealershipクラス、Carクラス、Wheelクラスについて知っている必要があるため、これらのいずれかを変更すると、呼び出しコードを変更する必要があります。後者の場合、コードはDealershipクラスについてのみ知る必要があります。また、両者の間には簡潔さまたは読みやすさについての議論もあります。
カプセル化の主な目的は何ですか?コードの他のすべての部分ですべてのプロパティとメソッドを使用できるようになると、コードはどのように見えますか?特定のクラスのプロパティとメソッドがシステムの他の多くの部分で使用され、それらをそのクラスの要素にバインドすることを期待しています。そのようなバインディングの何が問題になっていますか?これらの要素の一部は、この特定のクラスのリファクタリングまたはメンテナンス中に変更される可能性があるため、誤りです。そして、そのような要素を変更すると、すべての「クライアント」コードも変更する必要があります(「クライアントコード」とは、クラスのこの特定の要素を使用する他のコードを意味します)。そうすることは頭痛の種になるでしょう。
では、クラスのどの部分を非表示にする必要がありますか?少なくとも、クラスの動作を変更せずに将来変更できるもの。 「ここでSetを使用するか、Listを使用するほうが良いですか?両方のアプローチが機能します」、「私のアルゴリズムはこのタイプ/変数を使用しますか、それとも他の方法ですか?」そのような質問は、クラスのこの部分が内部実装であり、動作を達成するために選択した方法であることを警告するはずです。変更される可能性があり、1つの変数の型が変更されたためにアプリケーションの50%をリファクタリングしたくないので、この部分を他の部分から隠します。
ドメインドリブンデザインのアイデアに従うことで、何を公開すべきか、何を公開すべきでないかの決定に役立つことがわかりました。クラスが集約ルートの場合、それを公開できます。 Aggregate内の単なるエンティティの場合-できません。
あなたの質問への回答:あなたの車が将来ホイールを持たなくなる確率はどれくらいですか?高くない場合は、おそらく他のホイールに変更されないため、ホイールを公開できます。ただし、ホイールを保持する配列を公開することは適切な決定ではありません。いくつかの改造の後で、ホイールを収集し、必要な動作を行うシャーシのアイデアが不足していることに気付く可能性があります(私はあなたがそう主張することはありません-これは単なる例です)。 DDDに関して言えば、Wheelはモデルの現在の状態では有効な集約ルートのようです。
ところで。摩擦はホイールの特性ではありません。それはホイール自体とホイールが転がっている表面に依存します。 ;)
一方、2番目のバージョンでは、Carで定義されている多くの関数が直接の責任ではないため、単一責任の原則に違反しています。
上記の前提に同意しません。車の単一の責任は、ドライバーをポイントAからポイントBに導くことです。その責任は、エンジンに送る燃料の量や時期など、車のユーザーが知る必要のない多くの細部をカプセル化します。 。 (ユーザーは加速を要求しますが、自動車はそれを生成する方法を決定します。)車輪は間違いなく、人を目的地に導く単一の責任の一部です。そのため、ユーザーがホイールの摩擦を設定する必要がないことには同意しますが、2つ目の例の方が好きです。それが達成することになっているものは何でも、私はそれにアクセサーを作成しますが、それがホイールの摩擦を変更したか、タスクを達成するために何か他のことをしたかはクラス次第です。そして、そのようにインターフェースを作成することで、より良い方法が発生した場合に、将来的にそれを変更する余地が多くなります。