質問

100 を超えるプロパティを持つクラスを設計する場合に、どのようなアドバイス/提案/ガイダンスを提供しますか?

背景

  • このクラスは請求書を記述します。請求書には、それを説明する 100 を超える属性を含めることができます。日付、金額、コードなど...
  • 請求書を送信しているシステムは 100 の属性をそれぞれ使用し、(さまざまな部分が異なる時間に送信されるのではなく) 単一のエンティティとして送信されます。
  • 請求書を説明する属性は、ビジネス プロセスの一部として必要です。ビジネスプロセスは変更できません。

提案はありますか?

  • 100 個の属性を持つクラスの設計に直面したとき、他の人は何をしましたか?つまり、100 個のプロパティをそれぞれ持つクラスを作成しますか?
  • どうにかしてそれを壊しますか(そうするなら、どのようにして)?
  • それとも、これはあなたの経験ではごく普通の出来事ですか?

編集いくつかの素晴らしい回答を読み、これについてさらに考えてみたところ、この質問に対する単一の答えは実際には存在しないと思います。ただし、最終的には次の方針に沿ってデザインをモデリングすることになったので、 Lブシュキンさんの答え 私は彼の功績を認めました。最も人気のある答えではありませんが、LBrushkin の答えは、アプリケーション全体で集約して再利用するいくつかのインターフェイスを定義するのに役立ち、将来役立つ可能性のあるいくつかのパターンを調査するように促しました。

役に立ちましたか?

解決

これらのプロパティのいくつかはおそらく互いに関連していると思います。おそらく、グループとして意味のある請求書の独立したファセットを定義するプロパティのグループがあることを想像します。

請求書のさまざまなファセットをモデル化する個別のインターフェイスの作成を検討することもできます。これにより、これらのファセットを操作するメソッドとプロパティを、より一貫したわかりやすい方法で定義できます。

1つの大きなクラスの個別のプロパティとしてではなく、特定の意味を持つプロパティ(住所、場所、範囲など)を、集約するオブジェクトに結合することもできます。

問題をモデル化するために選択した抽象化と、他のシステム(またはビジネスプロセス)と通信するために必要な抽象化は同じである必要はないことに留意してください。実際、ブリッジパターンを適用して、個別の抽象化を独立して進化させることは生産的です。

他のヒント

データベーステーブルのように「正規化」を試みることができます。たとえば、アドレスに関連するすべてのプロパティをAddressクラスに入れてから、BillingAddressクラスにMailingAddressタイプのInvoiceおよび<=>プロパティを含めることができます。これらのクラスは、後で再利用することもできます。

悪いデザインは、明らかにあなたが提出しているシステムにあります-下位構造にグループ化できない100以上のプロパティを持つ請求書はありません。たとえば、請求書には顧客があり、顧客にはIDと住所があります。住所には、番地、郵便番号、その他が含まれます。ただし、これらのプロパティはすべて請求書に直接属しているべきではありません。請求書には顧客IDや郵便番号はありません。

これらすべてのプロパティを請求書に直接添付して請求書クラスを作成する必要がある場合は、顧客、住所、およびその他すべての必要なものに対して複数のクラスを使用してクリーンなデザインを作成し、それをうまくラップすることをお勧めしますすべての操作を背後のオブジェクトグラフに渡すだけで、ストレージとロジック自体を持たないファットインボイスクラスを使用して設計されたオブジェクトグラフ。

うーん...これらのすべては本当に請求書に具体的に関連しており、のみですか?通常、私が見たものは次のようなものです:

class Customer:
.ID
.Name

class Address
.ID 
.Street1
.Street2
.City
.State
.Zip

class CustomerAddress
.CustomerID
.AddressID
.AddressDescription ("ship","bill",etc)

class Order
.ID
.CustomerID
.DatePlaced
.DateShipped
.SubTotal

class OrderDetails
.OrderID
.ItemID
.ItemName
.ItemDescription
.Quantity
.UnitPrice

そしてすべてを結びつける:

class Invoice
.OrderID
.CustomerID
.DateInvoiced

請求書を印刷するとき、これらのすべてのレコードを結合します。

本当に100以上のプロパティを持つ単一のクラスが必要な場合は、辞書を使用する方がよい場合があります

Dictionary<string,object> d = new Dictionary<string,object>();
d.Add("CustomerName","Bob");
d.Add("ShipAddress","1600 Pennsylvania Ave, Suite 0, Washington, DC 00001");
d.Add("ShipDate",DateTime.Now);
....

ここでの考え方は、論理ユニットに分割することです。上記の例では、各クラスはデータベース内のテーブルに対応しています。これらのそれぞれをデータアクセスレイヤーの専用クラスに読み込むか、レポート(請求書)を生成するときにそれらが格納されているテーブルから結合して選択することができます。

code が実際に多くの場所で多くの属性を使用しない限り、代わりに辞書を探します。

実際のプロパティを持つことには利点があります(型安全性、発見可能性/インテリセンス、リファクタリング)ファイルなど。

格納するクラス/テーブルが正規化の規則に違反し始めると、列が多すぎます。

私の経験では、適切に正規化を行っているときに多くの列を取得することは非常に困難でした。幅の広いテーブル/クラスに正規化のルールを適用すると、エンティティごとの列数が少なくなると思います。

それは悪いOOスタイルと見なされますが、もしあなたがやっていることは、処理のためにそれらを先に渡すためにプロパティをオブジェクトに移入し、処理がプロパティを読むだけなら(おそらく他のオブジェクトまたはデータベースの更新を作成するために)、おそらくシンプルなPODオブジェクトが必要なものであり、すべてのパブリックメンバー、デフォルトコンストラクターを持ち、他のメンバーメソッドはありません。したがって、本格的なオブジェクトではなく、プロパティのコンテナとして扱うことができます。

辞書を使用しました<!> lt; string、string <!> gt;このようなもののために。

それはそれを処理できるたくさんの関数が付属しており、文字列を他の構造に変換したり、保存したりしやすいなどです。

純粋に美的観点から動機づけられるべきではありません。

コメントによると、このオブジェクトは基本的に、すべてのフィールドの存在を期待するレガシー システムによって消費されるデータ転送オブジェクトです。

この物体を部品から構成することに真の価値がない限り、その機能や目的を曖昧にすることで一体何が得られるのでしょうか?

以下のような正当な理由が考えられます。

1 - このオブジェクトの情報をさまざまなシステムから収集していますが、各部分は比較的独立しています。その場合、プロセスの考慮事項に基づいて最終オブジェクトを構成するのが合理的です。

2 - このオブジェクトのフィールドのさまざまなサブセットを使用できる他のシステムがあります。ここでの動機は再利用です。

3 - より合理的な設計に基づいた次世代の請求書発行システムの可能性が非常に現実的です。ここでは、システムの拡張性と進化が動機となっています。

これらの考慮事項がいずれもあなたのケースに当てはまらないとしたら、一体何が意味があるのでしょうか?

最終結果を得るには、約100個のプロパティを持つ請求書オブジェクトを作成する必要があるようです。どのような場合でも、そのようなプロパティは100個ありますか?たぶん、より小さなパラメータのセットが与えられた請求書を生成するクラスであるファクトリが必要でしょう。請求書の関連フィールドが関連するシナリオごとに、異なるファクトリメソッドを追加できます。

作成しようとしているのがテーブルゲートウェイであり、この他のサービスに既存の100列のテーブル、リストまたはディクショナリを使用すると、すぐに開始できます。ただし、大きなフォームまたはUIウィザードから入力を取得している場合は、おそらくリモートサービスに送信する前に内容を検証する必要があります。

単純なDTOは次のようになります。

class Form
{
    public $stuff = array();
    function add( $key, $value ) {}
}

テーブルゲートウェイは次のようになります。

class Form
{
    function findBySubmitId( $id ) {} // look up my form
    function saveRecord() {}       // save it for my session
    function toBillingInvoice() {} // export it when done
}

また、請求書のバリエーションがあるかどうかに応じて、非常に簡単に拡張できます。 (各サブクラスにvalidate()メソッドを追加することが適切な場合があります。)

class TPSReport extends Form { 
    function validate() {}
}

配信メカニズムはすべての請求書に共通しているため、DTOを配信メカニズムから分離する場合は、簡単です。ただし、請求書の成否に関連するビジネスロジックがある場合があります。そして、これは私が雑草に出かけていることです。しかし、それはどこにあり、オブジェクト指向モデルが役立つ可能性があります...私は異なる請求書と異なる請求書のための異なる手順があり、請求書提出barfsの場合、余分なルーチンが必要になるというペニーを支払うでしょう:-)

class Form { 
    function submitToBilling() {}
    function reportFailedSubmit() {}
    function reportSuccessfulSubmit() {}
}
class TPSReport extends Form { 
    function validate() {}
    function reportFailedSubmit() { /* oh this goes to AR */ }
}

David Livelysの回答に注意してください:それは良い洞察です。多くの場合、フォーム上のフィールドはそれぞれ独自のデータ構造であり、独自の検証ルールを持っています。そのため、複合オブジェクトを非常に迅速にモデリングできます。これにより、各フィールドタイプが独自の検証ルールに関連付けられ、より厳密な入力が強制されます。

検証をさらに進める必要がある場合、ビジネスルールは多くの場合、それらを提供するフォームまたはDTOとはまったく異なるモデリングです。また、部門別のロジックで、フォームとはほとんど関係のないロジックに直面することもあります。フォーム自体とモデル送信プロセスを個別に検証しないようにすることが重要です。

100列のテーブルではなく、これらのフォームの背後にスキーマを整理する場合、フィールド識別子と値でエントリをいくつかの列に分解するでしょう。

table FormSubmissions (
    id         int
    formVer    int -- fk of FormVersions
    formNum    int -- group by form submission
    fieldName  int -- fk of FormFields
    fieldValue text
)
table FormFields (
    id         int
    fieldName  char
)
table FormVersions (
    id
    name
)
select s.* f.fieldName from FormSubmissions s
left join FormFields f on s.fieldName = f.id
where formNum = 12345 ;

これは間違いなく、快適なものが見つかるまであなたのやり方をリファクタリングしたいケースだと思います。スキーマやオブジェクトモデルなどを制御できることを願っています。 (ところで...そのテーブルは「正規化された」ものとして知られていますか?そのスキーマのバリエーションを見てきましたが、通常はデータ型ごとに整理されています...良いですか?)

返されるすべてのプロパティが常に必要ですか?データを消費しているクラスでプロジェクションを使用し、必要なプロパティのみを生成できますか。

LINQを試すと、プロパティが自動生成されます。すべてのフィールドが複数のテーブルにまたがっており、ビューを構築してビューをデザイナーにドラッグできる場合。

辞書?なぜそうではないが、必ずしもそうではない。 C#タグが表示されます。あなたの言語には反射があり、あなたにとって良いものです。私のPythonコードにはこのような大きすぎるクラスがいくつかあり、リフレクションは非常に役立ちます。

for attName in 'attr1', 'attr2', ..... (10 other attributes):
    setattr( self, attName, process_attribute( getattr( self, attName ))

10個の文字列メンバーをエンコードからUNICODEに変換する場合、他の文字列メンバーは変更しないでください。他のメンバーに数値処理を適用します...型を変換します... forループビートコピー-清潔さのためにいつでも多くのコードを貼り付けます。

エンティティに100個のプロパティを持つ1つのクラスよりも100個のユニークな属性がある場合は、正しいことです。

アドレスのようなものをサブクラスに分割することは可能かもしれませんが、これはアドレスがそれ自体が実体であり、そのように簡単に認識されるためです。

教科書(つまり、現実世界では使いすぎない単純化された)の請求書は次のようになります:-

 class invoice:
    int id;
    address shipto_address;
    address billing_address;
    order_date date;
    ship_date date;
    .
    .
    . 
    line_item invoice_line[999];

  class line_item;
    int item_no.
    int product_id;
    amt unit_price;
    int qty;
    amt item_cost;
    .
    .
    .

だから、少なくともline_itemsの配列を持っていないことに驚いた。

それに慣れましょう!ビジネスの世界では、エンティティは数百、時には数千の一意の属性を簡単に持つことができます。

他のすべてが失敗した場合、少なくともクラスをいくつかの部分的なクラスに分割して読みやすくします。また、このクラスのさまざまな部分でチームが並行して作業しやすくなります。

幸運:)

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top