私たちは多くのユニットテストとビジネスオブジェクトのリファクタリングを行っており、クラスの設計についてveryは他のピアとは異なる意見を持っているようです。
私がファンではないクラスの例:
public class Foo
{
private string field1;
private string field2;
private string field3;
private string field4;
private string field5;
public Foo() { }
public Foo(string in1, string in2)
{
field1 = in1;
field2 = in2;
}
public Foo(string in1, string in2, string in3, string in4)
{
field1 = in1;
field2 = in2;
field3 = in3;
}
public Prop1
{ get { return field1; } }
{ set { field1 = value; } }
public Prop2
{ get { return field2; } }
{ set { field2 = value; } }
public Prop3
{ get { return field3; } }
{ set { field3 = value; } }
public Prop4
{ get { return field4; } }
{ set { field4 = value; } }
public Prop5
{ get { return field5; } }
{ set { field5 = value; } }
}
「実際の」クラスでは、これらはすべて文字列ではありませんが、完全にパブリックなプロパティ用に30のバッキングフィールドがある場合があります。
私はhateこのクラスで、私が好き嫌いだけかどうかはわかりません。いくつかの注意点:
実装者として、オブジェクトFoo
が常にどのような状態にあるかを知ることは、はるかに難しいと思います。 「オブジェクトの構築時にProp5
を設定するために必要な情報がない可能性があります。それは理解できると思いますが、その場合はProp5
セッターのみを公開し、クラスでは常に最大30のプロパティ。
「書くのが簡単(すべて公開)」というのではなく、「使いやすい」クラスを欲しがっているので、私はただひたすらとかクレイジーだと思いますか?上記のようなクラスは私に悲鳴を上げ、これがどのように使用されるのかわからないので、すべてを公開するだけです念のために。
私がひどくうるさくないなら、この種の考え方と闘うための良い議論は何ですか?私は論点を明確にするのが得意ではありません。私の意図を理解しようとすることに非常に不満を感じているからです(もちろん、意図的にではありません)。
完全にパブリックなクラスは、特定の状況と、1つのパブリックメソッドのみ(およびおそらく多くのプライベートメソッド)を持つ他の極端なクラスを正当化します。そして、いくつかのパブリックメソッドといくつかのプライベートメソッドもあるクラス。
それはすべて、それらを使用してモデル化する抽象化の種類、システムにどのレイヤーがあるか、さまざまなレイヤーで必要なカプセル化の程度、および(もちろん)クラスの作成者がどのような考え方を持っているかに依存します。から。これらのタイプはすべてSOLIDコードで確認できます。
どの種類のデザインをいつ好むかについて書かれた本が全部あるので、ここではそれについてのルールはリストしません。このセクションのスペースは十分ではないでしょう。ただし、クラスでモデル化したい抽象化の実例がある場合は、ここのコミュニティーが設計の改善に喜んでお手伝いすることでしょう。
他のポイントに対処するには:
だから代わりに
private string field1;
public string Prop1
{ get { return field1; } }
{ set { field1 = value; } }
書く
public string Prop1 { get;set;}
または
public string Prop1 { get;private set;}
「複数のコンストラクタ」:それ自体は問題ではありません。あなたの例に示すように、そこに不必要なコードの重複があるか、呼び出し階層が複雑である場合、問題が発生します。これは、共通部分を個別の関数にリファクタリングし、コンストラクタチェーンを一方向に編成することで簡単に解決できます。
「空のコンストラクターが原因で、プロパティに値が割り当てられない可能性があります」:C#では、すべてのデータ型に明確に定義されたデフォルト値があります。プロパティがコンストラクターで明示的に初期化されていない場合は、このデフォルト値が割り当てられます。これを使用する場合意図的に、完全に問題ありません。したがって、作者が何をしているのかを作者が知っている場合は、空のコンストラクタでも問題ない可能性があります。
「プロパティが多すぎます!(30の場合)」:はい、そのようなクラスをグリーンフィールドの方法で自由に設計できる場合、30は多すぎます。しかし、私たち全員がこの贅沢さを備えているわけではありません(以下のコメントにそれを書いていないのは、レガシーシステムですか?)。既存のデータベース、ファイル、またはサードパーティAPIのデータをシステムにマッピングする必要がある場合があります。したがって、これらのケースでは、30の属性が必要です。
あなたが言ったように、Foo
はビジネスオブジェクトです。ドメインに応じてメソッドの一部の動作をカプセル化する必要があり、そのデータは可能な限りカプセル化したままにする必要があります。
表示する例では、Foo
は [〜#〜] dto [〜#〜] のように見え、すべてのOOP原則(参照 貧血ドメインモデル )!
改善として私はお勧めします:
これは、次のような注意が必要なリファクタリングされたクラスになる可能性があります。
public sealed class Foo
{
private readonly string field1;
private readonly string field2;
private readonly string field3;
private readonly string field4;
public Foo() : this("default1", "default2")
{ }
public Foo(string in1, string in2) : this(in1, in2, "default3", "default4")
{ }
public Foo(string in1, string in2, string in3, string in4)
{
field1 = in1;
field2 = in2;
field3 = in3;
field4 = in4;
}
//Methods with business logic
//Really needed ?
public Prop1
{ get; }
//Really needed ?
public Prop2
{ get; }
//Really needed ?
public Prop3
{ get; }
//Really needed ?
public Prop4
{ get; }
//Really needed ?
public Prop5
{ get; }
}
ビジネスオブジェクトクラス設計のこの「完全にパブリック」な考え方に反対する方法
「これらのリファクタリングされたメソッドを単体テストできるようになりました。現状では、x、y、zの理由により、クライアントはテストできません。
上記の引数のいずれかを全体として行うことができる範囲で:
「プロパティにロジックのないプライベートバッキングフィールドは不要に見え、クラスを膨らませます」と言うことで、すでにまともな議論があります。
ここでは複数のコンストラクターが問題であるようには見えません。
すべてのプロパティをパブリックとして持つことについて...おそらくこのように考えると、スレッド間でこれらすべてのプロパティへのアクセスを同期したい場合、同期コードをすべての場所に記述しなければならない可能性があるため、大変なことになります。中古。ただし、すべてのプロパティがゲッター/セッターで囲まれている場合は、クラスに同期を簡単に構築できます。
あなたが「振る舞いをテストするのが難しい」と言ったとき、その議論はそれ自体が物語っています。テストでは、それがnullまたはそのようなものではないかどうかを確認する必要がある場合があります(プロパティが使用されるすべての場所)。あなたのテスト/アプリケーションがどのように見えるのかわからないので、私は本当にわかりません。
あなたは正しい方法ですが、プロパティが多すぎる場合は、継承を使用してみて(プロパティが十分に類似している場合)、ジェネリックを使用してリスト/配列を作成できますか?次に、ゲッター/セッターを記述して情報にアクセスし、すべてのプロパティを操作する方法を簡略化できます。