web-dev-qa-db-ja.com

「プリミティブオブセッション」を許可する正当な理由は、「ヨーヨー問題を回避する」ことですか。

によると、原始的な強迫観念がコードのにおいではないですか?、私は表現するためにZipCodeオブジェクトを作成する必要がありますStringオブジェクトの代わりにZipコード。

ただし、私の経験では、

public class Address{
    public String zipCode;
}

の代わりに

public class Address{
    public ZipCode zipCode;
}

後者の場合、プログラムを理解するためにZipCodeクラスに移動する必要があると思います。

そして、すべてのプリミティブデータフィールドがクラスに置き換えられたかどうかを確認するには、多くのクラス間を移動する必要があると思います。これは、 yo-yo問題 (anアンチパターン)。

だから私はZipCodeメソッドを新しいクラスに移動したいと思います、例えば:

古い:

public class ZipCode{
    public boolean validate(String zipCode){
    }
}

新着:

public class ZipCodeHelper{
    public static boolean validate(String zipCode){
    }
}

そのため、Zipコードを検証する必要のある人だけがZipCodeHelperクラスに依存します。そして、私は原始的な執着を維持することの別の「利点」を見つけました。たとえば、クラスがシリアル化された形式のように見えるようにします(たとえば、文字列列zipCodeを持つアドレステーブル)。

私の質問は、「クラスの定義間を移動する」「ヨーヨー問題を回避する」ことは「原始的な執着」を可能にする正当な理由ですか?

42
ocomfd

しないでください Addressクラスを理解するために、ZipCodeクラスにヨーヨーする必要があると想定しています。 ZipCodeが適切に設計されている場合、Addressクラスを読み取るだけで、ZipCodeが何をするかは明らかです。

プログラムはエンドツーエンドで読み取られるわけではありません。通常、プログラムは複雑すぎてこれを実現できません。プログラム内のすべてのコードを同時に覚えておくことはできません。したがって、抽象化とカプセル化を使用して、プログラムを意味のある単位に「チャンク」します。これにより、依存するすべてのコードを読み込まなくても、プログラムの一部(たとえば、Addressクラス)​​を確認できます。

たとえば、コードで文字列に遭遇するたびに、文字列のソースコードを読むようにしないでください。

クラスの名前をZipCodeからZipCodeHelperに変更すると、ZipコードとZipコードヘルパーという2つの異なる概念があることがわかります。したがって、2倍の複雑さになります。また、任意の文字列と有効な郵便番号は同じ型であるため、型システムでは区別できません。これが「執着」が適切な場所です。プリミティブの周りの単純なラッパータイプを避けたいという理由だけで、より複雑で安全性の低い代替案を提案しています。

この特定のタイプに応じて検証やその他のロジックがない場合、プリミティブを使用することは私見で正当化されます。ただし、ロジックを追加するとすぐに、このロジックが型でカプセル化されていると、はるかに簡単になります。

シリアライゼーションについては、使用しているフレームワークの制限のように思えます。確かに、ZipCodeを文字列にシリアル化したり、データベースの列にマップしたりできるはずです。

117
JacquesB

できる場合:

_new ZipCode("totally invalid Zip code");
_

ZipCodeのコンストラクタは次のことを行います。

_ZipCodeHelper.validate("totally invalid Zip code");
_

次に、カプセル化を解除し、ZipCodeクラスにかなりばかげた依存関係を追加しました。コンストラクタしないZipCodeHelper.validate(...)を呼び出す場合、実際には強制せずに、独自のアイランドにロジックを分離しています。無効な郵便番号を作成できます。

validateメソッドは、ZipCodeクラスの静的メソッドである必要があります。 「有効な」郵便番号の知識は、ZipCodeクラスと一緒にバンドルされています。コード例がJavaのように見える場合、不正な形式が指定された場合、ZipCodeのコンストラクターは例外をスローする必要があります。

_public class ZipCode {
    private String zipCode;

    public ZipCode(string zipCode) {
        if (!validate(zipCode))
            throw new IllegalFormatException("Invalid Zip code");

        this.zipCode = zipCode;
    }

    public static bool validate(String zipCode) {
        // logic to check format
    }

    @Override
    public String toString() {
        return zipCode;
    }
}
_

コンストラクターは形式をチェックして例外をスローし、無効な郵便番号が作成されるのを防ぎます。また、静的validateメソッドを他のコードで使用できるので、形式をチェックするロジックがZipCodeクラスにカプセル化されます。

このZipCodeクラスのバリアントには「yo-yo」はありません。それは、適切なオブジェクト指向プログラミングと呼ばれています。


ここでは、ZipCodeFormatまたはPostalService(PostalService.isValidPostalCode(...)、PostalService.parsePostalCode(...)など)と呼ばれる別のクラスが必要になる可能性がある国際化も無視します。

55
Greg Burghardt

この質問に多く取り組む場合、おそらく使用する言語は仕事に適したツールではありませんか?この種の「ドメインタイプのプリミティブ」は、たとえばF#で簡単に表現できます。

そこで、たとえば、次のように書くことができます。

type ZipCode = ZipCode of string
type Town = Town of string

type Adress = {
  zipCode: ZipCode
  town: Town
  //etc
}

let adress1 = {
  zipCode = ZipCode "90210"
  town = Town "Beverly Hills"
}

let faultyAdress = {
  zipCode = "12345"  // <-Compiler error
  town = adress1.zipCode // <- Compiler error
}

これは、さまざまなエンティティのIDの比較など、一般的な間違いを回避するのに非常に役立ちます。また、これらの型付きプリミティブは、C#またはJavaクラスよりもはるかに軽量であるため、実際に使用することになります。

11
Guran

答えは、郵便番号で実際に何をしたいかによります。 2つの極端な可能性があります。

(1)すべての住所が1つの国にあることが保証されています。全く例外はありません。 (たとえば、外国の顧客はいない、または外国の顧客のために働いている間、個人の住所が海外にある従業員はいない) 「現在はD4B 6N2ですが、2週間ごとに変更されます」など)。郵便番号は、住所指定だけでなく、支払い情報の検証や同様の目的にも使用されます。 -このような状況では、郵便番号クラスは非常に理にかなっています。

(2)住所はほとんどすべての国に存在する可能性があるため、郵便番号の有無にかかわらず(そして何千もの奇妙な例外や特別なケースを伴う)数十または数百のアドレス指定スキームが関連します。 「郵便番号」コードは、郵便番号が提供されていることを忘れないように、郵便番号が使用されている国の人々に思い出させるためにのみ要求されます。アドレスは、誰かが自分のアカウントへのアクセスを失い、自分の名前とアドレスを証明できる場合にのみ使用されるため、アクセスは復元されます。 -このような状況下では、すべての関連国の郵便番号クラスは多大な努力となるでしょう。幸い、それらはまったく必要ありません。

6
user144228

他の回答は、OOドメインモデリングと、より豊富なタイプを使用して値を表すことについて述べています。

特にあなたが投稿したサンプルコードを考えると、私は反対しません。

しかし、それが実際にあなたの質問のタイトルに答えるかどうかも疑問に思います。

次のシナリオを考えます(私が取り組んでいる実際のプロジェクトから引き出されます)。

中央サーバーと通信するリモートデバイスにフィールドデバイスがあります。デバイスエントリのDBフィールドの1つは、フィールドデバイスが存在するアドレスの郵便番号です。あなたは郵便番号(またはそのことについてのアドレスの残りのいずれか)を気にしません。それを気にするすべての人々は、HTTP境界の反対側にいます。あなたはたまたま、データの唯一の真の情報源です。ドメインモデリングには場所がありません。記録し、検証し、保存し、要求に応じてJSONブロブにシャッフルして他の場所に移動します。

このシナリオでは、SQL regex制約(またはそれに相当するORMの制約)を使用して挿入を検証する以外の多くのことを行うのはおそらく YAGNIの種類の過剰です。

3
Jared Smith

ZipCode抽象化は、AddressクラスにTownNameプロパティもない場合にのみ意味があります。それ以外の場合、抽象化は半分になります。郵便番号は町を指定しますが、これらの2つの関連する情報ビットは異なるクラスにあります。それはまったく意味がありません。

しかし、それでも、それはまだ原始的な強迫観念の正しいアプリケーション(またはむしろその解決策)ではありません。私が理解しているように、これは主に2つのことに焦点を当てています。

  1. 特にプリミティブのコレクションが必要な場合に、プリミティブをメソッドの入力(または出力)値として使用する。
  2. これらの一部を独自のサブクラスにグループ化する必要があるかどうかを再検討することなく、時間の経過とともに追加のプロパティを拡大するクラス。

あなたのケースはどちらでもありません。住所は、明確に必要なプロパティ(ストリート、ナンバー、郵便番号、町、州、国など)を持つ明確に定義された概念です。 singleの責任があるため、このデータを分割する理由はほとんどまたはまったくありません。地球上の場所を指定します。アドレスが意味を持つためには、これらのフィールドのallが必要です。住所の半分は無意味です。

これは、これ以上細分化する必要がないことを知る方法です。これをさらに分解すると、Addressクラスの機能的意図が損なわれます。同様に、Name(人物が添付されていない)がドメインで意味のある概念でない限り、PersonクラスでNameサブクラスを使用する必要はありません。それは(通常)そうではありません。名前は人を識別するために使用され、通常、それ自体では価値がありません。

2
Flater

記事から:

より一般的には、ヨーヨー問題は、概念を理解するために人がさまざまな情報源を切り替え続けなければならない状況を指すこともあります。

ソースコードは、 read よりも頻繁に記述されています。したがって、多くのファイルを切り替えなければならないというヨーヨーの問題が懸念されます。

ただし、 no の場合、相互に深く依存し合うモジュールやクラス(相互間でやり取りされる)を処理する場合、ヨーヨーの問題はより適切に感じられます。これらは読むべき特別な悪夢であり、ヨーヨー問題の造作家が考えていたものと思われます。

ただし- yes 、抽象化の層が多すぎないようにすることが重要です。

重要な抽象化はすべて、ある程度、漏れやすいものです。 -漏れやすい抽象化の法則。

たとえば、私は mmmaaa's answer で行われた仮定に同意しません。「あなたしないヨーヨーする必要がある[(visit)] ZipCodeクラスにアドレスクラスを理解するには」私の経験では、 do -少なくとも最初の数回はコードを読みました。ただし、他の人が指摘しているように、ZipCodeクラスが適切な場合が /回あります。

YAGNI(Ya Ai n't Gonna Need It)は、ラザニアコード(レイヤーが多すぎるコード)を避けるために従うべきより良いパターンです。タイプやクラスなどの抽象化は、プログラマーを支援するためにあり、 are 補助。

私は個人的に「コードの行を保存する」ことを目指しています(もちろん、関連する「ファイル/モジュール/クラスを保存する」など)。 「プリミティブにこだわる」の代名詞を私に当てはめる人がいると確信しています-ラベルについて心配するよりも、理由がわかりやすいのコードを使用する方が重要です。 、パターン、アンチパターン。関数、モジュール/ファイル/クラスを作成するとき、または関数を共通の場所に配置するときの正しい選択は、状況によって異なります。おおよそ3-100行の関数、80-500行のファイル、再利用可能なライブラリコードの「1、2、n」を目的としています( [〜#〜] sloc [〜#〜] -含まずコメントまたはボイラープレート;通常、必須のボイラープレートの行ごとに最低1つの追加のSLOCが必要です)。重要な点は、コードを書くときに人間の認識の限界に留意し、尊重することです。

ほとんどの肯定的なパターンは、まさにそれを行う開発者から、必要なときにで発生しました。同じ問題を解決するためにパターンを適用しようとするよりも、可読コードを書く方法を学ぶ方がはるかに重要です。優れた開発者なら誰でも、それが自分の問題に適切であるというまれなケースで、これまでに見たことがなく、ファクトリパターンを実装できます。私はファクトリーパターン、オブザーバーパターン、そしておそらく何百ものパターンを、それらの名前を知らずに使用しました(つまり、「変数割り当てパターン」はありますか?)。楽しい実験-JSに組み込まれているGoFパターンの数を確認してください language -2009年の12-15頃にカウントを停止しました。Factoryパターンはオブジェクトを返すのと同じくらい簡単ですたとえば、JSコンストラクターから-WidgetFactoryは必要ありません。

したがって、はい時々ZipCodeは良いクラスです。ただし、 no 、ヨーヨー問題は厳密には関係ありません。

1
Iiridayn

ヨーヨーの問題は、前後を反転させる必要がある場合にのみ関係します。これは、1つまたは2つの原因(場合によっては両方)が原因です。

  1. 悪い命名。 ZipCodeは問題ないように見えますが、ZoneImprovementPlanCodeはほとんどの人(そして感動しない人もいます)による外観を必要とするでしょう。
  2. 不適切なカップリング。 ZipCodeクラスに市外局番のルックアップがあるとします。便利なため理にかなっていると思うかもしれませんが、実際にはZipCodeとは関係がなく、それを考えると、どこに行くかわからなくなるということです。

名前を見て、それが何をするのかについて合理的な考えがあり、メソッドとプロパティがかなり明白なことを行う場合、コードを見る必要はなく、そのまま使用できます。それが、そもそもクラスの要点です。これらは、分離して使用および開発できるモジュール式のコードです。クラスの機能を確認するためにクラスのAPI以外のものを調べる必要がある場合、せいぜい部分的な失敗です。

0
jmoreno