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


9

私たちは多くのユニットテストとビジネスオブジェクトのリファクタリングを行っており、クラスの設計について他のピアとは非常に異なる意見を持っているようです。

私がファンではないクラスの例:

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のバッキングフィールドがある場合があります。

このクラスが嫌いで、私がうるさいだけなのかどうかわかりません。いくつかの注意点:

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

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

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

私がひどくうるさくないなら、この種の考え方と闘うための良い議論は何ですか?私は自分の要点を伝えようとすることに非常にイライラしているので(当然のことではありませんが)、私は引数を明確に表現することはあまり得意ではありません。



3
オブジェクトは常に有効な状態でなければなりません。つまり、オブジェクト部分的にインスタンス化する必要はありません。他の情報とは異なり、一部の情報はオブジェクトのコア状態の一部ではありません(たとえば、テーブルには脚が必要ですが、布はオプションです)。インスタンス化後にオプションの情報を提供できます。コア情報はインスタンス化時に提供する必要があります。一部の情報がコアであるが、インスタンス化時に不明な場合は、クラスにリファクタリングが必要だと思います。クラスのコンテキストを知らずにこれ以上言うことは難しい。
Marvin

1
「オブジェクト構築時にProp5を設定するために必要な情報がないかもしれない」と私に尋ねます。その情報がありますか?」この1つのクラスが表現しようとしているのは、実際には2つの異なるものがあるのか​​、あるいはこのクラスをより小さなクラスに分割する必要があるのか​​と思います。しかし、もしそれが「念のために」行われた場合、それもまた悪いことです、IMO。それは、オブジェクトがどのように作成/移入されるかについて明確なデザインが整っていないことを私に言います。
Wily博士の実習生

3
プロパティにロジックがないプライベートバッキングフィールドの場合、自動プロパティを使用しない理由はありますか?
Carson63000 2016年

2
@Kritner自動プロパティの全体の要点は、将来実際のロジックが必要になった場合、自動getまたはset:-)の代わりにシームレスに追加できるということです
Carson63000

回答:


10

完全にパブリックなクラスは、特定の状況だけでなく、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の属性が必要です。


ありがとう、そうです。POCO/ POJOが彼らの立場を持っていることは知っています。私の例は非常に抽象的なものです。小説に行かなければ、もっと具体的になるとは思えません。
Kritner 2016年

@Kritner:私の編集を参照してください
Doc Brown、

ええ、通常、私が真新しいクラスを作成しているとき、私は自動車物件ルートに行きます。「ただ」という理由でプライベートバッキングフィールドを持つことは(私の心の中で)不要であり、問​​題を引き起こしさえします。クラスに30個までのプロパティがあり、100個以上のクラスがある場合、プロパティの設定があるか、不適切なバッキングフィールドから取得されると言うのはそれほど難しいことではありません。 :)
Kritner 2016年

おかげで、文字列のデフォルト値はそうでnullはありませんか?そのため、で実際に使用されているプロパティの1つに.ToUpper()値が設定されていない場合、ランタイム例外がスローされます。これは、なぜ別の良い例です必要なクラスのデータがなければならない持っているオブジェクトの構築時に設定します。ユーザーに任せないでください。THanks
Kritner

未設定のプロパティが多すぎることに加えて、このクラスの主な問題は、ゼロロジック(ZRP)を持ち、貧血モデルの原型であることです。DTOのよ​​うに言葉で表現できる必要がないので、デザインは良くありません。
user949300 2016年

2
  1. 「プロパティにロジックのないプライベートバッキングフィールドは不要であり、クラスを膨らませている」と言っても、すでにまともな議論があります。

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

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

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

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


1
ありがとうございます。#1と#4私は間違いなく自分の主張をするのに最も快適だと感じています。#3の意味がよくわかりません。#5 クラスのほとんどのプロパティは、データベースの主キーを構成します。競合するキーを使用し、場合によっては最大8列ですが、それも別の問題です。キーを構成するパーツを独自のクラス/構造体に配置しようと考えていました。これにより、多くのプロパティが(少なくともこのクラスでは)削除されますが、これは、数千行のコードを持つ複数の発信者。だから、私は考えることはたくさんあると思います:)
Kritner

ええ クラスが不変の場合、クラス内に同期コードを記述する理由はありません。
RubberDuck 2016年

@RubberDuckこのクラスは不変ですか?どういう意味ですか?
スヌープ2016年

3
@StevieVは絶対に不変ではありません。プロパティセッターを持つクラスはすべて変更可能です。
RubberDuck 2016年

1
@Kritnerプロパティは主キーとしてどのように使用されますか?これにはおそらく、いくつかのロジック(nullチェック)またはルール(たとえば、NAMEがAGEよりも優先されます)が必要です。OOPによると、このコードはこのクラスに属しています。これを引数として使用できますか?
user949300

2

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

あなたが示す例ではFooDTOのように見え、すべてのOOP原則に反しています(貧血ドメインモデルを参照)。

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

  • このクラスをできるだけ不変にします。あなたが言ったように、あなたはいくつかのユニットテストをしたいと思います。ユニットテストの必須機能の 1つは決定論であり、決定論は副作用の問題を解決するため、不変性によって実現できます。
  • この神のクラスを、それぞれ1つだけを実行する複数のクラスに分割します(SRPを参照)。本当にPITAで30個の属性クラスを単体テストします。
  • メインコンストラクターを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

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

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

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

    • 機能は、再使用可能となります。
    • 乾いた
    • クライアントから切り離されています。
    • クラスに対するコーディングは高速で、エラーが発生しにくくなります。
  • 「よくできたクラスは状態を隠し、機能を公開します。これはOOクラスを設計するための最初の命題です。」
  • 「最終結果は、ビジネスドメインを表現するはるかに優れた仕事をします。異なるエンティティとそれらの明示的な相互作用です。」
  • 「この「オーバーヘッド」機能(つまり、明示的な機能要件ではありませんが、実行する必要があります)は際立っており、単一責任原則に準拠しています。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.