web-dev-qa-db-ja.com

ほとんどのクラスをデータフィールドのみのクラスとメソッドのみのクラス(可能な場合)に分離することは、良いパターンですか、それともアンチパターンですか?

たとえば、クラスには通常、クラスのメンバーとメソッドがあります。次に例を示します。

public class Cat{
    private String name;
    private int weight;
    private Image image;

    public void printInfo(){
        System.out.println("Name:"+this.name+",weight:"+this.weight);
    }

    public void draw(){
        //some draw code which uses this.image
    }
}

しかし、単一責任の原則とオープンクローズの原則について読んだ後、クラスをDTOと静的メソッドのみのヘルパークラスに分離することを好みます。

public class CatData{
    public String name;
    public int weight;
    public Image image;
}

public class CatMethods{
    public static void printInfo(Cat cat){
        System.out.println("Name:"+cat.name+",weight:"+cat.weight);
    }

    public static void draw(Cat cat){
        //some draw code which uses cat.image
    }
}

CatDataの責任はデータのみを保持することであり、メソッド(CatMethodsについても)を気にしないため、これは単一責任の原則に適していると思います。また、新しいメソッドを追加するときにCatDataクラスを変更する必要がないため、オープンクローズの原則にも適合します。

私の質問は、それは良いパターンですか、それともアンチパターンですか?

10
ocomfd

2つの極端な方法(「1つのオブジェクト内のすべてがプライベートですべて(おそらく無関係)なメソッド)と「すべてがパブリックで、オブジェクト内にメソッドがない」」を示しました。 IMHO良いOOモデリングはどれもない、スイートスポットは中央のどこかにあります。

どのメソッドまたはロジックがクラスに属し、何が外部に属しているかのリトマステストの1つは、メソッドが導入する依存関係を調べることです。追加の依存関係を導入しないメソッドは、指定されたオブジェクトの抽象化にうまく適合する限り問題ありません。追加の外部依存関係(図面ライブラリやI/Oライブラリなど)を必要とするメソッドは、あまり適していません。依存関係の注入を使用して依存関係をなくす場合でも、ドメインクラス内にそのようなメソッドを配置することが本当に必要かどうか、私は二度と考えません。

したがって、すべてのメンバーをパブリックにする必要も、クラス内のオブジェクトに対するすべての操作のメソッドを実装する必要もありません。これが代替案です:

public class Cat{
    private String name;
    private int weight;
    private Image image;

    public String getInfo(){
        return "Name:"+this.name+",weight:"+this.weight;
    }
    public Image getImage(){
        return image;
    }
}

Catオブジェクトは、すべての属性を公開することなく、周囲のコードがprintInfoおよびdrawを簡単に実装できるようにする十分なロジックを提供します。これら2つのメソッドの適切な場所は、おそらくCatMethodsの神クラスではありません(printInfodrawはおそらく異なる懸念事項であるため、それらが属している可能性は非常に低いと思います同じクラスに)。

CatDrawingControllerを実装するdrawを想像できます(そしてCanvasオブジェクトを取得するために依存性注入を使用している可能性があります)。コンソール出力を実装し、getInfoを使用する別のクラスも想像できます(したがって、このコンテキストではprintInfoは廃止される可能性があります)。しかし、これについて賢明な決定を行うには、コンテキストとCatクラスが実際にどのように使用されるかを知る必要があります。

これが実際に私が解釈した方法です Fowler's Anemic Domain Model批評家 -一般に再利用可能なロジック(外部依存関係なし)の場合、ドメインクラス自体は適切な場所なので、それらに使用する必要があります。しかし、それはそこにロジックを実装することを意味するものではなく、まったく逆です。

上記の例では、(im)可変性についての決定をする余地が残っていることにも注意してください。 Catクラスがセッターを公開せず、Image自体が不変である場合、この設計はCatを不変にすることを可能にします(DTOアプローチでは不可能です)。しかし、不変性が必要ない、または役に立たないと思う場合は、その方向に進むこともできます。

10
Doc Brown

遅い答えが、私は抵抗することはできません。

XのほとんどのクラスはYに適していますか、それともアンチパターンですか?

ほとんどの場合、ほとんどのルールは、何も考えずに適用され、ほとんどがひどく間違っています(これを含む)。

意図的ではなく、必死になって起こった、すぐ下の、素早くて汚い、手続き型のコードの混乱の中のオブジェクトの誕生についての物語をお話ししましょう。

私のインターンと私はペアプログラミングで、ウェブページをこするための使い捨てコードをすばやく作成しています。このコードが長く存続することを期待する理由は絶対にないので、機能するものを打ち出しています。ページ全体を文字列として取得し、必要なものをあなたが想像できる最も驚くほど壊れやすい方法で切り抜きます。判断しないでください。できます。

これを行っている間に、チョッピングを行うための静的メソッドをいくつか作成しました。私のインターンは、あなたのCatDataによく似たDTOクラスを作成しました。

私が最初にDTOを見たとき、それは私を悩ませました。何年にもわたる損傷Java=脳に影響を与えたため、私はパブリックフィールドで反動しました。しかし、私たちはC#で作業していました。C#は、データは不変、または後でカプセル化されます。インターフェースを変更せずに、いつでも好きなときに追加できます。たぶん、ブレークポイントを設定できるからです。すべて、クライアントにそれについて通知することなく、そうです。C#。ブーJava。

それで私は舌を抱えました。私が静的メソッドを使用して、これを使用する前に初期化するのを見ました。そのうちの約14人がいた。醜いですが、気にする理由がありませんでした。

その後、他の場所でそれを必要としました。コードをコピーして貼り付けたいと思っていました。 14行の初期化が行われています。辛くなり始めました。彼はためらってアイデアを求めました。

しぶしぶ「私は物を考えますか?」と尋ねました。

彼は自分のDTOを振り返り、混乱して顔を台無しにしました。 「オブジェクトです」。

「私は実際のオブジェクトを意味します」

「え?」

「何かお見せしましょう。役立つかどうかはあなたが決めます」

私は新しい名前を選び、すぐに次のようなものを作成しました。

public class Cat{
    CatData(string catPage) {
        this.catPage = catPage
    }
    private readonly string catPage;

    public string name() { return chop("name prefix", "name suffix"); }
    public string weight() { return chop("weight prefix", "weight suffix"); }
    public string image() { return chop("image prefix", "image suffix"); }

    private string chop(string prefix, string suffix) {
        int start = catPage.indexOf(prefix) + prefix.Length;
        int end = catPage.indexOf(suffix);
        int length = end - start;
        return catPage.Substring(start, length);
    }
}

これは、静的メソッドがまだ実行していないことは何もしませんでした。しかし今、私は14の静的メソッドをクラスに吸い込んで、作業対象のデータと一緒に一人でいることができるようにしました。

私のインターンにそれを使わせることを強制しませんでした。私はそれを提供し、静的メソッドを使い続けるかどうかを彼に決定させました。私は彼がおそらく彼がすでに働いていたものに固執すると思って家に帰りました。翌日、彼がたくさんの場所でそれを使用しているのを見つけました。それはまだ醜くて手続き的な残りのコードを整理しましたが、この少しの複雑さはオブジェクトの後ろに私たちから隠されました。もう少し良かったです。

これにアクセスするたびに、かなりの作業が行われていることを確認してください。 DTOは、ニースの高速キャッシュ値です。私はそれを心配しましたが、必要な場合は、使用しているコードに触れずにキャッシュを追加できることに気付きました。気になるまで気にしません。

常にOO DTOを超えるオブジェクトに固執する必要がありますか?いいえ、メソッドを移動できないようにする境界を越える必要があるとき、DTOは輝きます。DTOはその場所を持っています。

OOオブジェクト。両方のツールを使用する方法を学びます。それぞれの費用を学びます。問題、状況、およびインターンが決定する方法を学びます。ドグマはここではあなたの友達ではありません。


私の答えはすでに途方もなく長いので、コードのレビューでいくつかの誤解を誤解させます。

たとえば、クラスには通常、クラスのメンバーとメソッドがあります。例:

public class Cat{
    private String name;
    private int weight;
    private Image image;

    public void printInfo(){
        System.out.println("Name:"+this.name+",weight:"+this.weight);
    }

    public void draw(){
        //some draw code which uses this.image
    }
}

あなたのコンストラクタはどこですか?これは、それが有用であるかどうかを知るのに十分ではありません。

しかし、単一責任の原則とオープンクローズの原則について読んだ後、クラスをDTOと静的メソッドのみのヘルパークラスに分離することを好みます。例:

public class CatData{
    public String name;
    public int weight;
    public Image image;
}

public class CatMethods{
    public static void printInfo(Cat cat){
        System.out.println("Name:"+cat.name+",weight:"+cat.weight);
    }

    public static void draw(Cat cat){
        //some draw code which uses cat.image
    }
}

CatDataの責任はデータのみを保持することであり、メソッド(CatMethodsの場合も)を気にしないため、これは単一責任の原則に適していると思います。

単一責任原則の名の下に、多くのばかげたことをすることができます。 Cat StringsとCat intは分離する必要があると主張できます。その描画メソッドと画像はすべて独自のクラスを持つ必要があります。実行中のプログラムは単一の責任であるため、クラスは1つだけにしてください。 :P

私にとって、単一責任の原則に従う最善の方法は、ボックスに複雑さを入れて非表示にできる優れた抽象化を見つけることです。あなたがそれに良い名前を付けることができれば、人々が彼らの内部を見たときに見つけたものに驚かされないように、あなたはそれをかなりよくフォローしました。それがより多くの決定を命令するとそれが問題を求めていることを期待します。正直なところ、両方のコードリストがそうしているので、SRPがここで重要な理由はわかりません。

また、新しいメソッドを追加するときにCatDataクラスを変更する必要がないため、オープンクローズの原則にも適合します。

うーん、ダメ。オープンクローズの原則は、新しいメソッドを追加することではありません。古いメソッドの実装を変更でき、何も編集する必要がないということです。古い方法ではなく、あなたを使用するものはありません。代わりに、別の場所に新しいコードを記述します。ポリモーフィズムのいくつかの形式はそれをうまく行います。ここには表示されません。

私の質問は、それは良いパターンですか、それともアンチパターンですか?

さて地獄どうやって私は知っておくべきですか?見てください、どちらにしてもそれには利点とコストがあります。コードをデータから分離すると、もう一方を再コンパイルすることなく変更できます。多分それはあなたにとって非常に重要です。多分それはあなたのコードを無意味に複雑にするだけです。

それが気分が良くなれば、マーティンファウラーが パラメータオブジェクト と呼ぶものからそれほど遠くないでしょう。オブジェクトにプリミティブだけを取り込む必要はありません。

私がしてほしいことは、どちらのコーディングスタイルでも、分離する方法とそうでない方法を理解することです。信じられないかもしれませんが、スタイルを選択する必要はありません。あなたはただあなたの選択で生きなければなりません。

6
candied_orange

10年以上にわたって開発者の間で議論を引き起こしている討論のトピックに偶然遭遇しました。 2003年、 Martin Fowlerは "Anaemic Domain Model"(ADM) というフレーズを作り、このデータと機能の分離を説明しました。彼と彼に同意する他の人々は、「リッチドメインモデル」(データと機能の混合)は「適切なオブジェクト指向」であり、ADMアプローチは非オブジェクト指向のアンチパターンであると主張します。

この議論を却下する意見は常にあり、議論のその側面は、近年、機能開発技法の開発者がより多く採用するようになったことにより、ますます大胆になってきています。そのアプローチは、データと機能の懸念の分離を積極的に奨励します。データは可能な限り不変である必要があるため、可変状態のカプセル化は問題になりません。このような状況では、関数をそのデータに直接アタッチしてもメリットはありません。そのとき「オブジェクト指向ではない」かどうかは、そのような人々にはまったく関係ありません。

フェンスのどちら側に座っているかに関係なく(私は「Martin Fowlerが古いtoshの負荷を話している」側のBTWに非常にしっかりと座っています)、printInfoおよびdrawの静的メソッドの使用は、ほぼ普遍的に嫌われています。単体テストを作成するときに静的メソッドをモックするのは困難です。したがって、それらに副作用(画面や他のデバイスへの印刷または描画など)がある場合、それらは静的であってはならず、またはパラメーターとして出力場所が渡される必要があります。

だからあなたはインターフェースを持つことができます:

_public interface CatMethods {
    void printInfo(Cat cat);
    void draw(Cat cat);
}
_

そして、実行時にシステムの残りの部分に注入される実装(他の実装がテストに使用されています):

_internal class CatMethodsForScreen implements CatMethods {
    public void printInfo(Cat cat) {
        System.out.println("Name:"+cat.name+",weight:"+cat.weight);
    }

    public void draw(Cat cat) {
        //some draw code which uses cat.image
    }
}
_

または、パラメーターを追加して、これらのメソッドから副作用を削除します。

_public static class CatMethods {
    public static void printInfo(Cat cat, OutputHandler output) {
        output.println("Name:"+cat.name+",weight:"+cat.weight);
    }

    public static void draw(Cat cat, Canvas canvas) {
        //some draw code which uses cat.image and draws it on canvas
    }
}
_
2
David Arno

DTO-データ転送オブジェクト

それだけに役立ちます。プログラムまたはシステム間でデータをシャントする場合は、データ構造とフォーマットのみに関係するオブジェクトの管理可能なサブセットを提供するため、DTOが望ましいです。利点は、複数のシステムにわたって複雑なメソッドへの更新を同期する必要がないことです(基礎となるデータが変更されない限り)。

OOの要点は、データとそのデータに作用するコードを緊密にバインドすることです。論理オブジェクトを個別のクラスに分離することは、通常、悪い考えです。

0
James Anderson