web-dev-qa-db-ja.com

「原始的な執着を避ける」と「できるだけ抽象的なタイプを使用する」は互いに矛盾しますか?

「プリミティブオブセッション」を許可する正当な理由は「ヨーヨー問題を回避することですか?」 によると、「価格」を次のように定義する必要があります。

public class Main{
    private Price price;
}

public class Price{
    private double priceValue;
    public static void roundToValidValue(double priceValue){
    }
}

の代わりに

public class Main{
    private double price;
}

public class PriceHelper{
    public static double roundToValidValue(double priceValue){
    }
}

保守性を高めるために、Priceに関するロジックをクラスにカプセル化する必要があるためです。しかし、「できるだけ抽象的な型を使用する」という目標に反しているように見えました( 実装(HashMap)ではなく、インターフェース(例:Map)を使用してJavaオブジェクトを定義する理由) ):

私が理解しているように、「できるだけ抽象的な型をできるだけ使用する」ことの目標の1つは、クラス間の結合を減らすことです。これにより、クライアントは呼び出さないメソッドの使用を回避できるため、クライアントはメソッドに依存しません。彼らが使用しないこと。ただし、Priceクラスに戻ると、「roundToValidValue」をまったく呼び出さないクライアントがあることがわかりました。 「プリミティブな強迫観念を回避する」ルールに従い、「roundToValidValue」を新しいクラスにカプセル化すると、「roundToValidValue」をまったく呼び出さないクライアントも「roundToValidValue」に依存し、「デカップリング」の目的に反するように見えます。 「可能な限り最も抽象的なタイプを使用する」で示唆されている。

また、 すべてのタイプを定義する必要がありますか? に従って、プリミティブタイプを使用してビジネスモデルを直接表すことは避けます。つまり、

public class Car{
    private double price;
    private double weight;
    public void methodForPrice(double price){
    }
}

public class Class{
    private Price price;
    private Weight weight;
    public void methodForPrice(Price price){
    }
}

これにより、間違ったメソッド、つまり:methodForPrice(weight)を呼び出す状況を回避できます。ただし、- 実装(HashMap)ではなくインターフェイス(たとえば、マップ)を使用してJavaオブジェクトを定義する理由 によると、可能な限り最も抽象的な型を使用することが推奨されます。

public LinkedHashMap<String,Stock> availableStocks;
public HashMap<String,Stock> unusedStocks;

public class Main{
    public void removeUnusedStocks(Map<String,Stock> unusedStocks){
    }
}

「原始的な強迫観念」の問題に苦しんでいる可能性があることに気づきました。「removeUnusedStocks(availableStocks)」を誤って呼び出すことがあります。より具体的なタイプを使用すると思います:

public class Main{
    public void removeUnusedStocks(LinkedHashMap<String,Stock> unusedStocks){
    }
}

両方のバージョンがひどく機能する場合でも優れているため、「できるだけ抽象的なタイプを使用する」ことは「原始的な執着を避ける」という目標に反すると思います。

その結果、「プリミティブオブセッションを回避する」と「できるだけ抽象的なタイプをできるだけ使用する」という目標は相反するものであり、「プリミティブオブセッションを回避する」と「できるだけ抽象的なタイプをできるだけ使用する」という目標は互いに矛盾していると思います。 ?

3
aacceeggiikk

あなたの例で理解すべき重要な部分は、Priceすべきではないdouble--と同じです。まず、doubles金額を正しく表すことができません。ただし、Priceには他の制限があります-マイナスにすることはできません。小数点以下3桁(USの場合)を超えることはできません。これらはtype invariantsです。タイプレベルで条件を適用することで、ミスを防ぎます。 Weightについても同様です。これらを一緒に使用すると、PriceWeightに追加できなくなります-これは良いことです。

これらの理由により、doublePriceまたはWeightを抽象化したものではありません。それらは異なる目的を持つ完全に異なるタイプです。 Priceは、Weightと同じように使用することはできません。つまり、doubleを使用することはできません(使用する型であっても)内部で値を保存します)。抽象化は、具象型と同じ重要な不変式を保持する必要があります。 HashMapsMapsであり、Mapを使用できる任意の方法で使用できます。

8
mgw854

doubleは抽象型ではないことに注意してください。それは具体的なものです。 Priceも抽象型ではありません。

すべてのスカラー型を抽象化するAbstractScalarTypeがある場合でも、AbstractScalarTypeはメソッドroundToValidValueを指定できます(ただし、必ずしも実装する必要はありません)。そうは言っても、アプリケーションのそのようなクラスにはおそらくほとんど価値がありません。意味のない抽象クラスを発明しないでください。それらから継承できるからです。

最も抽象的な型を使用するという原則は、メソッドが受け入れる型を主に指します。物事のリストを分析してリストの内容の概要を生成するメソッドがある場合、そのメソッドが特定の種類のリスト(たとえば、単一リンクのソートされたリスト)でのみ機能することを指定する理由はありません。 anyリストの種類を受け入れる必要があります。

3
Simon B

「ほとんどの抽象型を使用する」というルールの作成者は、おそらく単一フィールドクラスをそのコンテンツで置き換えることを意味していませんでした。結局のところ、doublePriceのスーパータイプではありません。むしろinterface Priceこれは、異なるPriceImplクラスで実装できます。ここで使用することが正当化されるのであれば、別の質問です(私の意見ではそうではありません)。

1
max630

"definethe Java実装ではなくインターフェース "は、

宣言時に最も抽象的な型を使用し、作成時に最も特殊な型を使用します

したがって、できるだけ抽象的な型を使用すると言うと、宣言時にを忘れます。主なアイデアは

  1. 必要に応じて実装を簡単に変更
  2. すべてのサブケースに同じ方法を使用します。

その場合、removeStocks(Map<String,Stock> stocks)は、未使用の在庫の削除(店舗での在庫など)と使用可能な在庫の削除(カートからなど)の両方に使用できます。タイプセーフは多くのエラーを防ぐことができますが、すべてではないことに注意してください。store.removeStocks(available)は論理的なバグです。その理由でのみavailableStocksLinkedHashMapとして宣言すると、大きなエラー(パフォーマンスと保守性の両方)になります。


一方、それは重要な部分です可能な限り。重みには制限があるため、プリミティブdoubleを使用するのは不可能です。 LinkedHashMap<> availableStocksHashMap<> availableStocksに変更しても何も変化しないため、より一般的なMapを使用することをお勧めします


TL; DRそれらが矛盾する場合は、おそらくそれらの1つを間違った方法で使用しています。レビューの時間です。

0
ADS