web-dev-qa-db-ja.com

ビジネスオブジェクトクラス設計のこの「完全にパブリック」な考え方に反対する方法

私たちは多くのユニットテストとビジネスオブジェクトのリファクタリングを行っており、クラスの設計について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このクラスで、私が好き嫌いだけかどうかはわかりません。いくつかの注意点:

  • プロパティにロジックがないプライベートバッキングフィールドは不要と思われ、クラスを膨らませます
  • 複数のコンストラクター(やや良い)と組み合わせて
  • すべてのプロパティにパブリックセッターがあり、私はファンではありません。
  • 空のコンストラクターが原因で、プロパティに値が割り当てられない可能性があります。呼び出し元が知らない場合、非常に望ましくない動作のテストが困難になる可能性があります。
  • プロパティが多すぎます! (30件の場合)

実装者として、オブジェクトFooが常にどのような状態にあるかを知ることは、はるかに難しいと思います。 「オブジェクトの構築時にProp5を設定するために必要な情報がない可能性があります。それは理解できると思いますが、その場合はProp5セッターのみを公開し、クラスでは常に最大30のプロパティ。

「書くのが簡単(すべて公開)」というのではなく、「使いやすい」クラスを欲しがっているので、私はただひたすらとかクレイジーだと思いますか?上記のようなクラスは私に悲鳴を上げ、これがどのように使用されるのかわからないので、すべてを公開するだけです念のために

私がひどくうるさくないなら、この種の考え方と闘うための良い議論は何ですか?私は論点を明確にするのが得意ではありません。私の意図を理解しようとすることに非常に不満を感じているからです(もちろん、意図的にではありません)。

9
Kritner

完全にパブリックなクラスは、特定の状況と、1つのパブリックメソッドのみ(およびおそらく多くのプライベートメソッド)を持つ他の極端なクラスを正当化します。そして、いくつかのパブリックメソッドといくつかのプライベートメソッドもあるクラス。

それはすべて、それらを使用してモデル化する抽象化の種類、システムにどのレイヤーがあるか、さまざまなレイヤーで必要なカプセル化の程度、および(もちろん)クラスの作成者がどのような考え方を持っているかに依存します。から。これらのタイプはすべてSOLIDコードで確認できます。

どの種類のデザインをいつ好むかについて書かれた本が全部あるので、ここではそれについてのルールはリストしません。このセクションのスペースは十分ではないでしょう。ただし、クラスでモデル化したい抽象化の実例がある場合は、ここのコミュニティーが設計の改善に喜んでお手伝いすることでしょう。

他のポイントに対処するには:

  • 「プロパティにロジックのないプライベートバッキングフィールド」:はい、そうです。ささいなゲッターとセッターの場合、これは不必要な「ノイズ」です。この種の「膨らみ」を回避するために、C#にはプロパティのget/setメソッドのショートカット構文があります。

だから代わりに

   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の属性が必要です。

10
Doc Brown

あなたが言ったように、Fooはビジネスオブジェクトです。ドメインに応じてメソッドの一部の動作をカプセル化する必要があり、そのデータは可能な限りカプセル化したままにする必要があります。

表示する例では、Foo[〜#〜] dto [〜#〜] のように見え、すべてのOOP原則(参照 貧血ドメインモデル )!

改善として私はお勧めします:

  • このクラスを immutable として可能な限り作成します。あなたが言ったように、あなたはいくつかのユニットテストをしたいと思います。 1つの 単体テストの必須機能は決定論です 、そして 副作用 の問題を解決するため、不変性を介して決定論を実現できます。
  • この god class を、それぞれ1つの処理のみを実行する複数のクラスに分割します( [〜#〜] srp [〜#〜] を参照)。 30個の属性クラスを実際にaでユニットテスト [〜#〜] pita [〜#〜]
  • 1 メインコンストラクター と他のメインコンストラクターのみを呼び出すことで、コンストラクターの冗長性を削除します。
  • 不要なゲッターを削除します。すべてのゲッターが本当に役立つのか、真剣に疑っています。
  • ビジネスクラスにビジネスロジックを復活させます。これが属しています。

これは、次のような注意が必要なリファクタリングされたクラスになる可能性があります。

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

ビジネスオブジェクトクラス設計のこの「完全にパブリック」な考え方に反対する方法

  • 「クライアントクラスには、「単なるDTO義務」以上のことを行ういくつかのメソッドが散在しています。それらは一緒に属しています。」
  • 「それは「DTO」かもしれないが、それはビジネスアイデンティティを持っている-私たちはEqualsを上書きする必要がある。」
  • 「これらをソートする必要があります-IComparableを実装する必要があります」
  • 「これらのプロパティのいくつかをつなぎ合わせています。ToStringをオーバーライドして、すべてのクライアントがこのコードを書く必要がないようにしましょう。」
  • 「このクラスに対して同じことをする複数のクライアントがいます。DRYコードをアップする必要があります。」
  • 「クライアントはこれらのコレクションを操作しています。それはカスタムコレクションクラスである必要があることは明らかです。以前の多くの点で、そのカスタムコレクションは現在よりも機能的になっています。」
  • 「すべての退屈で公開された文字列操作を見てください。ビジネスに関連するメソッドにカプセル化することで、コーディングの生産性が向上します。」
  • 「これらのメソッドを一緒にクラスにプルすると、より複雑なクライアントクラスを処理する必要がないため、テストが可能になります。」
  • 「これらのリファクタリングされたメソッドを単体テストできるようになりました。現状では、x、y、zの理由により、クライアントはテストできません。

  • 上記の引数のいずれかを全体として行うことができる範囲で:

    • 機能が再利用可能になります。
    • 乾いた
    • クライアントから切り離されています。
    • クラスに対するコーディングは高速で、エラーが発生しにくくなります。
  • 「よくできたクラスは、状態を隠して機能を公開します。これは、OOクラスを設計するための最初の命題です。」
  • 「最終結果は、ビジネスドメインを表現するはるかに優れた仕事をします。個別のエンティティとそれらの明示的な相互作用です。」
  • 「この「オーバーヘッド」機能(つまり、明示的な機能要件ではありませんが、実行する必要があります)は際立っており、単一責任原則に準拠しています。
2
radarbob
  1. 「プロパティにロジックのないプライベートバッキングフィールドは不要に見え、クラスを膨らませます」と言うことで、すでにまともな議論があります。

  2. ここでは複数のコンストラクターが問題であるようには見えません。

  3. すべてのプロパティをパブリックとして持つことについて...おそらくこのように考えると、スレッド間でこれらすべてのプロパティへのアクセスを同期したい場合、同期コードをすべての場所に記述しなければならない可能性があるため、大変なことになります。中古。ただし、すべてのプロパティがゲッター/セッターで囲まれている場合は、クラスに同期を簡単に構築できます。

  4. あなたが「振る舞いをテストするのが難しい」と言ったとき、その議論はそれ自体が物語っています。テストでは、それがnullまたはそのようなものではないかどうかを確認する必要がある場合があります(プロパティが使用されるすべての場所)。あなたのテスト/アプリケーションがどのように見えるのかわからないので、私は本当にわかりません。

  5. あなたは正しい方法ですが、プロパティが多すぎる場合は、継承を使用してみて(プロパティが十分に類似している場合)、ジェネリックを使用してリスト/配列を作成できますか?次に、ゲッター/セッターを記述して情報にアクセスし、すべてのプロパティを操作する方法を簡略化できます。

2
Snoop