単純なドメインオブジェクトを表すプリミティブvsクラス?


14

domain-speciifcオブジェクトとプレーンな文字列または数字を使用する場合の一般的なガイドラインまたは経験則は何ですか?

例:

  • 年齢階級と整数?
  • FirstNameクラスとString?
  • UniqueID vs String
  • PhoneNumberクラスvs文字列vs Long?
  • DomainNameクラスとString?

ほとんどのOOP実践者は、間違いなくPhoneNumberとDomainNameの特定のクラスを言うと思います。それらを有効にするものとそれらを比較する方法に関するルールが増えると、単純なクラスをより簡単かつ安全に処理できるようになります。しかし、最初の3つについては、さらに議論があります。

私は「年齢」クラスに出会ったことはありませんが、非負でなければならないので理にかなっていると主張することができます(負の年齢について議論できることはわかっていますが、プリミティブ整数とほぼ同等である良い例です)。

文字列は「名」を表すのが一般的ですが、空の文字列は有効な文字列ですが、有効な名前ではないため、完全ではありません。通常、比較は大文字小文字を無視して行われます。空のチェック、大文字と小文字を区別しない比較などを行う方法はありますが、これを行うにはコンシューマーが必要です。

答えは環境に依存しますか?私は主に、おそらく10年以上にわたって存続し、維持されるエンタープライズ/高価値ソフトウェアに関心を持っています。

おそらく私はこれを考えすぎていますが、クラスとプリミティブのどちらを選択するかについてのルールがあるかどうかを知りたいと思います。


2
年齢クラスは、整数に置き換えることができる場合、良いアイデアではありません。コンストラクタで生年月日を取得し、getCurrentAge()およびgetAgeAt(Date date)メソッドがある場合、クラスには意味があります。ただし、整数に置き換えることはできません。
キウィロン

5
「文字列は文字列ではない場合があります。それに応じてモデル化します。」「原始的な強迫観念」についてのマーク・シーマンによって良い記事があります:blog.ploeh.dk/2015/01/19/...
Serhii Shushliapin

4
実際、Ageクラスは奇妙な方法で意味をなします。西洋の年齢の一般的な定義は、出生時にゼロであり、次の誕生日に1を追加します。東アジアの方法論では、あなたは出生時に1人であり、翌年の新年に1人が追加されます。したがって、ロケールに応じて、年齢を異なる方法で計算できます。EG from en.wikipedia.org/wiki/East_Asian_age_reckoning people may be one or two years older in Asian reckoning than in the western age system
ピーターM

@PeterM、それは良い情報です!日付/時刻と現在の年齢。それらは単純な概念である必要がありますが、これは私たちが処理に使用しているものよりもはるかに複雑な世界があることを証明しています。
マチャド

6
この質問の下に多くの文章がありますが、答えはそれほど複雑ではありません。あなたは使いAgeますが、このようなクラスが提供するであろうと、追加の動作を必要とするときの代わりに整数のクラスを。
ロバートハーベイ

回答:


19

domain-speciifcオブジェクトとプレーンな文字列または数字を使用する場合の一般的なガイドラインまたは経験則は何ですか?

一般的なガイドラインは、ドメイン固有の言語でドメインをモデリングすることです。

考慮:整数を使用する理由 すべての整数を文字列で簡単に表すことができます。またはバイト付き。

整数年齢のプリミティブ型を含むドメインに依存しない言語でプログラミングしている場合、どちらを選択しますか?

実際のところ、特定のタイプの「プリミティブ」な性質は、実装のための言語選択の偶然です。

特に、数値には通常、追加のコンテキストが必要です。年齢は単なる数字ではなく、次元(時間)、単位(年?)、丸め規則もあります! 年齢を一緒に追加すると、お金に年齢を追加しても意味がありません。

タイプを明確にすることで、未検証メールアドレスと検証メールアドレスの違いをモデル化できます

これらの値がメモリ内でどのように表現されるかという事故は、最も興味深い部分の1つです。ドメインモデルCustomerIdは、int、String、UUID / GUID、またはJSONノードを気にしません。アフォーダンスが欲しいだけです。

整数がビッグエンディアンかリトルエンディアンかを本当に気にしますか?List渡されたものが配列またはグラフの抽象化であるかどうかを気にしますか?倍精度の算術演算が非効率的であり、浮動小数点表現に変更する必要があることがわかった場合、ドメインモデルは気にする必要がありますか?

パルナス、1972年に書きました

代わりに、困難な設計決定または変更される可能性のある設計決定のリストから始めることを提案します。各モジュールは、そのような決定を他のモジュールから隠すように設計されています。

ある意味では、導入するドメイン固有の値の種類は、データのどの表現を使用するかを決定するモジュールです。

そのため、利点はモジュール性です。変更の範囲を管理しやすい設計になります。欠点はコストです- 必要なオーダーメイドのタイプを作成するのはより手間がかかります。正​​しいタイプを選択するには、ドメインをより深く理解する必要があります。値モジュールの作成に必要な作業量は、Blubのローカル方言によって異なります。

方程式のその他の用語には、ソリューションの予想寿命(一度実行されるスクリプトウェアの慎重なモデリングと投資収益率の低さ)、ドメインがビジネスの中核能力にどれだけ近いかなどが含まれます。

私たちが考えるかもしれない1つの特別なケースは境界を越えたコミュニケーションのそれです。1つの展開可能なユニットを変更するには、他の展開可能なユニットとの調整された変更が必要になるような状況にはなりたくありません。したがって、メッセージは、不変条件やドメイン固有の動作を考慮せずに、表現により重点を置く傾向があります。「この値は厳密に正でなければならない」メッセージ形式で通信するのではなく、ワイヤ上でその表現を伝え、ドメイン境界でその表現に検証を適用します。


困難で変化しそうなものを隠す(または抽象化する)ことの優れた点。
user949300

4

あなたは抽象化を考えていますが、それは素晴らしいことです。しかし、あなたのリストは、ほとんどがより大きな何かの個々の属性であるように思えます(もちろん、すべてが同じものではありません)。

私がより重要だと思うのは、クライアントプログラマーが複数の属性ではなく、より高いレベルの抽象化を扱う必要がないように、属性をグループ化し、Personクラスなどの適切なホームを与える抽象化を提供することです。

この抽象化が得られたら、単なる値である属性の追加の抽象化は必ずしも必要ありません。Personクラスは、BirthDateを(プリミティブ)日付として、PhoneNumbersを(プリミティブ)文字列のリストとして、Nameを(プリミティブ)文字列として保持できます。

書式設定コード付きの電話番号がある場合、これは2つの属性であり、電話番号のクラスに値します(おそらく、番号のみを取得するか、書式設定された電話番号を取得するなど、何らかの動作があります)。

名前が複数の属性(たとえば、プレフィックス/タイトル、ファースト、ラスト、サフィックス)としてあり、クラスに値する場合(現在は、フルネームを文字列として取得するか、小さなピースやタイトルを取得するなどの動作があります) 。)。


1

必要に応じてモデル化する必要があります。一部のデータが必要な場合はプリミティブ型を使用できますが、これらのデータに対してさらに操作を行う場合は、特定のクラスでモデル化する必要があります(コメントで@kiwironに同意します) Ageクラスには意味がありませんが、Birthdateクラスに置き換えて、そこにこれらのメソッドを配置する方が良いでしょう。

クラス内でこれらの概念をモデル化する利点は、モデルの豊かさを実施することです。たとえば、代わりに任意の日付を受け入れるメソッドがあり、ユーザーは任意の日付、第二次世界大戦の開始日、または冷戦終了日を入力できます。これらの日付のうち、まだ有効な誕生日です。それをBirthdateに置き換えることができます。この場合、メソッドは、日付を受け入れないようにBirthdateを受け入れます。

FirstNameについては、UniqueIDもPhoneNumberもあまりメリットがありません。たとえば、一般的にPhoneNumberは国と地域を指すため、一部のオペレーターは定義済みの番号サフィックスを使用してモバイルを分類するため、国と地域を提供するように依頼できます、Office ...番号です。したがって、これらのメソッドをそのドメインに追加できます。これはDomainNameと同じです。

詳細については、DDD(ドメインドリブンデザイン)の値オブジェクトをご覧になることをお勧めします。


1

依存するのは、そのデータに関連付けられた動作(維持または変更、あるいはその両方)があるかどうかです。

要するに、それが関連付けられた多くの振る舞いなしに放り込まれる単なるデータの断片である場合、プリミティブ型を使用するか、またはより複雑なデータの断片については、(ほとんど振る舞わない)データ構造(例えば、ゲッターとセッター)。

一方、決定を下すために、コード内のさまざまな場所(多くの場合、互いに近接していない場所)でこのデータを確認または操作していることに気付いた場合は、クラスを定義して適切なオブジェクトに変換しますその中に関連する動作をメソッドとして配置します。何らかの理由でこのようなクラスを定義しても意味がないと思われる場合は、この動作をこのデータを操作する何らかの「サービス」クラスに統合することもできます。


1

あなたの例は、何か他のもののプロパティや属性によく似ています。つまり、プリミティブだけで開始することは完全に合理的であるということです。ある時点でプリミティブを使用する必要があるため、通常は実際に必要なときに複雑さを省きます。

たとえば、私がゲームを作っているとしましょう。キャラクターの老化を心配する必要はありません。そのために整数を使用することは完全に理にかなっています。年齢は、キャラクターが何かをすることが許可されているかどうかを確認するための簡単なチェックに使用できます。

他の人が指摘したように、あなたの地域によっては年齢は複雑な問題になる可能性があります。異なるロケールなどで年齢を調整する必要がある場合は、プロパティAgeを持ちDateOfBirthLocaleプロパティとしてクラスを導入するのが最適です。そのAgeクラスは、任意のタイムスタンプで現在の年齢も計算できます。

そのため、プリミティブをクラスに昇格させる理由がありますが、それらはアプリケーション固有のものです。ここではいくつかの例を示します。

  • 情報の種類(電話番号、メールアドレスなど)を使用するすべての場所に適用する必要がある特別な検証ロジックがあります。
  • 人間が読むためにレンダリングするために使用される特別なフォーマットロジックがあります(IP4およびIP6アドレスなど)
  • すべてそのオブジェクトタイプに関連する一連の関数があります。
  • 同様の値タイプを比較するための特別なルールがあります

基本的に、プロジェクト全体で一貫して使用する値の処理方法に関するルールが多数ある場合は、クラスを作成します。機能にとってそれほど重要ではないため、単純な値で生活できる場合は、プリミティブを使用します。


1

結局のところ、オブジェクトとは、あるプロセスが実際に実行されたという目撃者です

プリミティブとintは、一部のプロセスが4バイトのメモリをゼロにし、-2,147,483,648から2,147,483,647の範囲の整数を表すために必要なビットを反転させたことを意味します。これは年齢の概念についての強力な声明ではありません。同様に、(非ヌル)System.Stringは、いくつかのバイトが割り当てられ、ビットが反転されて、一部のエンコードに関してUnicode文字のシーケンスを表すことを意味します。

したがって、ドメインオブジェクトの表現方法を決定するときは、ドメインオブジェクトが証人として機能するプロセスについて考える必要があります。FirstName本当に空でない文字列である必要がある場合、システムは、ユーザーに渡された文字のシーケンスで少なくとも1つの非空白文字が作成されることを保証するプロセスを実行した証人として立つ必要があります。

同様に、Ageオブジェクトはおそらく、何らかのプロセスが2つの日付の差を計算した証人になるはずです。のでints、一般的に2つの日付の間の差を計算した結果ではない、彼らは、年齢を表す不十分事実上です。

したがって、ほとんどの場合、各ドメインオブジェクトのクラス(単なるプログラム)を作成する必要があることは明らかです。だから問題は何ですか?問題は、C#(私が気に入っている)とJavaがクラスを作成する際にあまりにも多くの式を要求することです。しかし、ドメインオブジェクトの定義をより簡単にする代替構文を想像できます。

//hypothetical syntax
class Age = |start:date - end:date|.Years

(* ML-like syntax *)
type Age(x, y) = datediff(year, x, y)

//C#-like syntax with primary constructors and expression-bodied classes
class Age(DateTime x, DateTime y) => implicit operator int (Age a) => Abs((y - x).Years);

たとえば、F#には、実際にはActive Patternsの形式でこれに適した構文があります。

//Impossible to produce an `Age` without first computing the difference of two dates
//But, any pair (tuple) of dates is implicitly converted to an `Age` when needed.

let (|Age|) (x, y) =  (date_diff y x).Days / 365

ポイントは、プログラミング言語がドメインオブジェクトを定義するのを面倒にするからといって、実際にそうするのが悪い考えだということではないということです。


†これらの条件ポスト条件とも呼ばれますが、プロセスが実行できることと実行されたことの証拠として役立つため、「証言」という用語を好みます。


0

クラスとプリミティブの使用から得られるものについて考えてください。クラスを使用するとあなたを得ることができる場合

  • 検証
  • 書式設定
  • 他の便利な方法
  • タイプセーフティ(つまり、PhoneNumberクラスの場合、電話番号が必要な文字列またはランダムな文字列(つまり「キュウリ」)に割り当てることはできません)
  • その他の利点

そして、これらの利点はあなたの人生を楽にし、コードの品質、読みやすさ改善し、そのようなことを使うことの苦痛を上回ります。それ以外の場合は、プリミティブに固執します。それが近い場合、判断の呼び出しを行います。

また、適切なネーミングで処理できる場合もあることを理解してください(つまり、名前付きストリングとfirstName、ストリングプロパティーをラップするだけのクラス全体の作成)。物事を解決する方法はクラスだけではありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.