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


10

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

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クラスを変更する必要がないため、オープンクローズの原則にも適合します。

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


15
あなたが新しい概念を学んだので、どこでも盲目的に何かをすることは常にアンチパターンです。
カヤマン2018年

4
クラスを変更するメソッドからデータを分離することが常に優れているとしたら、クラスのポイントは何でしょうか?それは非オブジェクト指向プログラミングのように聞こえます(確かにその位置があります)。これは「オブジェクト指向」とタグ付けされているので、OOPが提供するツールを使用したいと思いますか?
user1118321

4
SRPはひどく誤解されているため禁止する必要があることを証明する別の質問。一連のパブリックフィールドにデータを保持することは責任を負いません
user949300

1
@ user949300、クラスがそれらのフィールドを担当している場合、それらをアプリの他の場所に移動しても、もちろん影響はありません。または別の言い方をすると、あなたは間違っています:彼らは100%責任を負います。
David Arno

2
@DavidArnoおよびR Schmitz:フィールドを含むことは、SRPの目的に対する責任としてカウントされません。含まれている場合、すべてがDTOであるため、OOPが存在しない可能性があり、別のクラスにデータを処理するプロシージャが含まれます。単一の責任は単一の利害関係者に関係します。(これは、プリンシパルの反復ごとに少し変わるようですが)
user949300

回答:


10

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

どのメソッドまたはロジックがクラスに属し、何が外部に属しているかのリトマステストの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、すべての属性を公開することなく、周囲のコードがandを簡単に実装できるようにします。これらの2つの方法のための適切な場所は、おそらく神クラスではないCatMethods(ので、printInfodraw私は彼らが同じクラスに属するものは非常に低いと思い、おそらく異なる懸念されています)。

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

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

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


ハッピーダウンボッターをトリガーしてください、これは退屈になります。批評家がいたら教えてください。誤解があれば、明確にさせていただきます。
Doc Brown、

私は概ねあなたの答えに同意しますgetInfo()が、この質問をする人を誤解させるかもしれません。プレゼンテーションの責任を微妙に混ぜ合わprintInfo()せ、DrawingControllerクラスの目的を打ち破っています
Adriano Repetti

@AdrianoRepettiそれがの目的に反しているとは思わないDrawingController。私はそれに同意しgetInfo()printInfo()恐ろしい名前です。考えてみましょうtoString()printTo(Stream stream)
candied_orange

@率直に言って、それは名前だけではなく、それが何をするかについてです。このコードはプレゼンテーションの詳細に関するものであり、処理されたコンテンツとそのロジックではなく、出力を効果的に抽象化しただけです。
Adriano Repetti

@AdrianoRepetti:正直なところ、これは元の質問に合わせて私のポイントを実証するための、単なる工夫された例です。実際のシステムでは、もちろん、より良いメソッドと名前についての決定を可能にする周囲のコンテキストがあります。
ドクターブラウン

6

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

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

ほとんどの場合、ほとんどのルールは、何も考えずに適用されると、恐ろしく間違ったものになります(これを含む)。

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

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

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

私が最初にDTOを見たとき、それは私を悩ませました。何年にもわたってJavaが私の脳に与えたダメージは、私が公共の場で反動する原因となりました。しかし、私たちはC#で作業していました。C#では、データを不変にしたり、後でカプセル化したりする権利を維持するために、時期尚早のゲッターやセッターは必要ありません。インターフェイスを変更せずに、いつでも追加できます。たぶん、ブレークポイントを設定できるようにするためです。クライアントにそれについて何も言わずに。いや、C#。Boo 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は高速でキャッシュされた値です。私はそれを心配しましたが、使用しているコードに一切手を触れることなく、必要に応じてキャッシュを追加できることに気付きました。気になるまで気にしません。

常にDTOよりもOOオブジェクトに固執する必要があると言っていますか?いいえ。メソッドの移動を妨げる境界を越える必要がある場合、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クラスを変更する必要がないため、オープンクローズの原則にも適合します。

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

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

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

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

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


2

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

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

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

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

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
    }
}

しかし、なぜ最初から関数型言語を使うのではなく、JavaのようなOO言語を関数型言語に変えようとするのでしょうか。ある種の「私はJavaが嫌いですが、誰もがそれを使用しているので、私はそうしなければなりませんが、少なくとも私は自分のやり方でそれを書くつもりです」。OOパラダイムは時代遅れであり、何らかの形で時代遅れであると主張しているのでない限り。私は、「Xが必要な場合、どこで入手できるかを知っている」という考え方を取り入れています。
カヤマン2018年

@カヤマン、「私は「Xが欲しいなら、どこで手に入れられるか」という考え方の学校に加入しています。私はしません。私はその態度は近視眼的で少し攻撃的であると思います。Javaは使用していませんが、C#を使用しており、「実際のOO」機能の一部を無視しています。継承やステートフルオブジェクトなどは気にせず、多くの静的メソッドとシールされた(最終的な)クラスを記述します。C#を中心としたツールとコミュニティの規模は、他の追随を許しません。F#の場合は、はるかに貧弱です。そこで、「Xが必要な場合は、現在のツールにXを追加するように依頼する」という考え方を取り入れています。
David Arno

1
言語の側面を無視することは、他の言語の機能を組み合わせて一致させることとは大きく異なります。プログラミングのような不正確な科学のルール(または原則)をたどるだけではうまくいかないので、「本当のOO」を書こうともしません。もちろん、あなたはそれらを破ることができるようにルールを知る必要があります。機能を盲目的に攻撃することは、言語の開発にとって悪いことです。違和感はないが、FPシーンの一部である私見は「FPはスライスされたパン以来の最高のものであり、なぜ人々はそれを理解しないのか」と感じているようである。OOやJavaが「最高」だとは決して言いません(または考えません)。
カヤマン

1
@カヤマンですが、「他の言語の機能を組み合わせてマッチングしようとする」とはどういう意味ですか?「Javaはオブジェクト指向言語である」ため、関数型機能をJavaに追加してはならないと主張していますか?もしそうなら、それは私の見解では近視眼的な議論です。開発者の要望に合わせて言語を進化させましょう。代わりに別の言語に切り替えることを強制するのは間違ったアプローチだと思います。確かに、一部の「FP擁護者」は、FPがこれまでで最高のものであることについてトップを超えています。また、一部の「OO擁護者」は、OOが「明らかに優れているために戦いに勝った」ことについて、トップに立っています。両方とも無視してください。
David Arno

1
私は反FPではありません。便利なJavaで使用しています。しかし、プログラマが「これが欲しい」と言った場合、それはクライアントが「これが欲しい」と言っているようなものです。プログラマーは言語デザイナーではなく、クライアントはプログラマーではありません。OPの「解決策」は不快だとあなたが言ったので、私はあなたの答えに賛成しました。質問のコメントはもっと簡潔ですが、つまり、OO言語で手続き型プログラミングを再発明しました。しかし、あなたが示したように、一般的な考えには意味があります。非貧血ドメインモデルは、データと動作が排他的であることを意味せず、外部のレンダラーまたはプレゼンターは禁止されます。
カヤマン2018年

0

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

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

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


-1

多くの設計パターンと原則は矛盾しており、最終的には、優れたプログラマーとしての判断だけが、特定の問題にどのパターンを適用すべきかを決定するのに役立ちます。

私の意見では、設計をより複雑にする強い理由がない限り、デフォルトで、「作業を可能にする最も単純なことを実行する原則が優先されます。したがって、具体的な例では、最初の設計を選択します。これは、同じクラスにデータと関数を組み合わせた、より伝統的なオブジェクト指向のアプローチです。

ここでは、アプリケーションについて自問できるいくつかの質問を示します。

  • 猫の画像をレンダリングする別の方法を提供する必要がありますか?もしそうなら、継承はそれを行う便利な方法ではありませんか?
  • 私の猫クラスは別のクラスから継承する必要があるか、インターフェースを満たす必要がありますか?

これらのいずれかに「はい」と答えると、DTOクラスと関数クラスが別々になり、より複雑な設計になる可能性があります。


-1

それは良いパターンですか?

人々はさまざまな考え方に従っているため、おそらくここでさまざまな答えが得られるでしょう。だから私が始める前に:この回答はオブジェクト指向プログラミングによるもので、 "Clean Code"という本でRobert C. Martinが説明しています。


「真の」OOP

私の知る限り、OOPには2つの解釈があります。ほとんどの人にとって、それは「オブジェクトはクラスである」に要約されます。

ただし、「オブジェクトは何かを実行するものである」という別の考え方の方が役立つと思いました。クラスを使用している場合、各クラスは「データホルダー」またはオブジェクトの2つのいずれかになります。データ保持者はデータを保持し(duh)、オブジェクトはデータを保持します。これは、データ所有者がメソッドを持てないということではありませ!aのことを考えてみてくださいlist。A listは実際に何もしませんが、データの保持方法を強制するためのメソッドを持っています

データホルダー

あなたCatはデータホルダーの典型的な例です。確かに、プログラムの外では猫は何かをするものですが、プログラムの内部でCatは、a はString name、int weightおよびImage imageです。

そのデータの所有者はいないまた、彼らは、ビジネスロジックが含まれていないことを意味するわけではない何かを!あなたの場合はCatクラスが必要なコンストラクタを持っているnameweightそしてimage、あなたは成功し、すべての猫がそれらを持っていることをビジネスルールをカプセル化してきました。クラスの作成weight後にを変更できる場合Cat、それはカプセル化された別のビジネスルールです。これらは非常に基本的なものですが、それは、それらを誤解しないことが非常に重要であることを意味します。このようにして、その誤解を確実に防ぐことができます。

オブジェクト

オブジェクトは何かをします。Clean *オブジェクトが必要場合は、「オブジェクトは1つのことを行う」に変更できます。

猫の情報を印刷することはオブジェクトの仕事です。たとえば、このCatInfoLoggerメソッドをパブリックメソッドで呼び出すことができますLog(Cat)。外部から知る必要があるのはこれだけです。このオブジェクトは猫の情報をログに記録します。これを行うには、が必要Catです。

内側では、このオブジェクトは、猫を記録するという単一の責任を果たすために必要なものへの参照を持ちます。この場合、これはSystemfor への参照にすぎませんSystem.out.printlnが、ほとんどの場合、他のオブジェクトへのプライベート参照になります。印刷する前に出力をフォーマットするロジックが複雑になりすぎるCatInfoLoggerと、新しいオブジェクトへの参照を取得できますCatInfoFormatter。後で、すべてのログもファイルに書き込む必要がある場合は、それを行うCatToFileLoggerオブジェクトを追加できます。

データホルダーとオブジェクト-概要

さて、なぜそんなことをするのですか?これは、クラスを変更すると1つだけ変更されるためです(例では猫がログに記録される方法)。オブジェクトを変更する場合は、特定のアクションの実行方法のみを変更します。データホルダーを変更する場合は、保持されるデータとその保持方法のみを変更します。

データの変更は、何かが行われる方法も変更する必要があることを意味する可能性がありますが、責任のあるオブジェクトを変更して自分で実現させるまで、その変更は起こりません。

やりすぎ?

このソリューションは少しやり過ぎに見えるかもしれません。率直に言って:それはです。プログラム全体が例と同じくらい大きい場合は、上記のいずれも行わないでください。提案されている方法のいずれかをコイントスで選択してください。他のコードない場合でも、他のコードを混乱させる危険はありません。

ただし、「深刻な」(=スクリプトまたは2以上の)ソフトウェアはおそらく、このような構造から利益を得るのに十分な大きさです。


回答

ほとんどのクラスをDTOとヘルパークラスに分離することは良いことでしょうか...

「ほとんどのクラス」(それ以上の修飾なし)をDTO /データホルダーに変換することは、実際にはパターンではなく、多かれ少なかれランダムであるため、アンチパターンではありません。

ただし、データとオブジェクトを分離しておくことは、コードをクリーンに保つための良い方法と見なされています*。場合によっては、フラットな「貧血」DTOのみが必要になることもあります。また、データの保持方法を強制する方法が必要な場合もあります。プログラムの機能をデータに含めないでください。


*これは、本のタイトルのように大文字のCが付いた "クリーン"です。最初の段落を覚えておいてください。これは1学問程度ですが、他にもあるからです。

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