OOPのゼロ動作オブジェクト-私の設計のジレンマ


94

OOPの背後にある基本的な考え方は、データと動作(そのデータに基づく)は不可分であり、クラスのオブジェクトの考え方によって結合されるということです。オブジェクトには、それ(およびその他のデータ)で機能するデータとメソッドがあります。明らかに、OOPの原則により、単なるデータであるオブジェクト(C構造体など)はアンチパターンと見なされます。

ここまでは順調ですね。

問題は、私のコードが最近このアンチパターンの方向にますます進んでいるように見えることに気づいたことです。クラスと疎結合設計の間の情報隠蔽を達成しようとすればするほど、私のクラスは、動作クラスのない純粋なデータとデータクラスのないすべての動作の混合になります。

私は通常、他のクラスの存在に対する認識を最小限に抑え、他のクラスのインターフェイスに関する知識を最小限に抑える方法でクラスを設計します。特にトップダウン方式でこれを実施します。低レベルのクラスは高レベルのクラスについては知りません。例えば:

一般的なカードゲームAPIがあるとします。あなたにはクラスがありCardます。ここで、このCardクラスはプレーヤーの可視性を決定する必要があります。

1つの方法はboolean isVisible(Player p)Cardクラスに参加することです。

もう1つはboolean isVisible(Card c)Playerクラスに参加することです。

特に、最初のアプローチは嫌いです。なぜなら、より高いレベルのPlayerクラスに関する知識をより低いレベルのCardクラスに与えるからです。

代わりに、3番目のオプションを選択しました。このViewportクラスでは、指定Playerされたカードとカードのリストによって、どのカードが表示されるかが決まります。

ただし、このアプローチは、可能なメンバー関数の両方CardPlayerクラスを奪います。カードの可視性以外のものに対してこれを行うとCardPlayerすべての機能が他のクラスで実装されているため、純粋にデータを含むクラスが残りますViewport

これは、OOPの基本的な考え方に明らかに反しています。

正しい方法はどれですか?クラスの相互依存関係を最小化し、想定される知識と結合を最小化するが、すべての低レベルクラスにデータのみが含まれ、高レベルクラスにすべてのメソッドが含まれるという奇妙なデザインに巻き込まれないようにするにはどうすればよいですか?全体の問題を回避するクラス設計に関する3番目の解決策や視点はありますか?

PS別の例を示します。

DocumentId不変で、単一のBigDecimal idメンバーとこのメンバーのゲッターのみを持つクラスがあるとします。ここで、データベースからこのidをDocumentId返すメソッドをどこかに持つ必要がありDocumentます。

あなたは:

  • クラスにDocument getDocument(SqlSession)メソッドを追加しDocumentId、永続性に関する知識("we're using a database and this query is used to retrieve document by id")、DBへのアクセスに使用されるAPI などを突然導入します。また、このクラスでは、コンパイルするために永続性JARファイルが必要になりました。
  • この方法でいくつかの他のクラスを追加しDocument getDocument(DocumentId id)たまま、DocumentId死んだ、いや行動、構造体のようなクラスとしてクラスを。

21
ここにあるあなたの前提のいくつかは完全に間違っており、それが根本的な質問に答えることを非常に難しくしています。質問はできる限り簡潔かつ意見のないものにしてください。より良い答えが得られます。
pdr 14

31
「これは明らかにOOPの主要な考え方に反しています」-いいえ、そうではありませんが、よくある誤解です。
ドク・ブラウン

5
問題は、過去に「オブジェクト指向」のさまざまな学校があったという事実にあると思います-元々はアラン・ケイのような人々によって意図されていた方法です(geekswithblogs.net/theArchitectsNapkin/archive/2013/09/08/ …)、およびその方法は、Rationalの人々(en.wikipedia.org/wiki/Object-oriented_analysis_and_design)によってOOA / OODのコンテキストで教えられました。
ドックブラウン14

21
これは非常に良い質問であり、よく投稿されています-他のコメントのいくつかに反して、私は言います。プログラムを構成する方法に関するほとんどのアドバイスがいかに素朴であるか不完全であるか、そしてそれを行うのがどれだけ難しいか、どれだけ正しくしようとしても多くの状況で適切な設計が到達不能であるかを明確に示しています。そして、特定の質問に対する明白な答えは複数の方法ですが、設計の根本的な問題は持続します。
チアゴシルバ14

5
振る舞いのないクラスはアンチパターンだと誰が言いますか?
ジェームズアンダーソン14

回答:


42

あなたが説明するものは貧血領域モデルとして知られています。多くのOOP設計原則(デメテルの法則など)と同様に、ルールを満たすためだけに後方に曲げる価値はありません。

ランドスケープ全体を散らかさず、他のオブジェクトに頼って自分でできるハウスキーピングを行わない限り、価値のあるバッグを持っていることに関して何も問題はありません。

プロパティを変更するためだけに別のクラスがあれば、それは確かにコードの匂いになりますCard-それ自体でそれらの世話をすることが合理的に期待できるなら。

しかし、それは本当にそれが見えるものCardを知ることの仕事Playerですか?

そして、なぜ実装Card.isVisibleTo(Player p)ではなくPlayer.isVisibleTo(Card c)?またはその逆?

はい、あなたはあなたのために何らかのルールを考え出すことができます- (?)Playerよりも高いレベルであるようにCard-しかし、それは推測するのは簡単ではなく、私は複数の場所を見る必要がありますメソッドを見つけます。

時間が経つにつれてisVisibleTo両方 CardPlayerクラスの両方で実装するという腐ったデザインの妥協につながる可能性がありますが、それはノーノーだと思います。なんでそうなの?恥ずかしい日player1.isVisibleTo(card1)card1.isVisibleTo(player1).私が考えると異なる値を返す日をすでに想像しているので-それは主観的です-これは設計によって不可能にれるべきです。

カードとプレイヤーの相互の可視性は何らかの文脈オブジェクトによって支配されるべきです-それViewportであるDealGame

グローバル機能を持​​つこととは異なります。結局のところ、多くの同時ゲームがあるかもしれません。同じカードを多くのテーブルで同時に使用できることに注意してください。Cardスペードのエースごとに多くのインスタンスを作成しますか?

私はまだisVisibleToonを実装するかもしれませんがCard、それにコンテキストオブジェクトを渡しCard、クエリをデリゲートします。高いカップリングを回避するためにインターフェイスするプログラム。

2番目の例については、ドキュメントIDがのみで構成されている場合BigDecimal、なぜそれのラッパークラスを作成するのですか?

必要なのは DocumentRepository.getDocument(BigDecimal documentID);

ちなみに、Javaはありませstructんが、C#にはがあります。

見る

参考のため。これは非常にオブジェクト指向の言語ですが、だれも大したことはしません。


1
C#の構造に関するメモ:Cで知っているような典型的な構造ではありません。実際、継承、カプセル化、ポリモーフィズムによるOOPもサポートしています。いくつかの特性に加えて、主な違いは、インスタンスが他のオブジェクトに渡されるときにランタイムがインスタンスを処理する方法です。構造体は値型であり、クラスは参照型です。
アシュラット14

3
@Aschratt:構造体は継承をサポートしていません。構造体はインターフェイスを実装できますが、インターフェイスを実装する構造体は、同様に実行するクラスオブジェクトとは異なる動作をします。構造体をオブジェクトのように動作させることは可能ですが、C構造体のように動作する何かが必要な場合の構造体の最適なユースケース、およびカプセル化するものはプリミティブまたは不変のクラス型です。
supercat

1
なぜ「グローバル機能を持​​つのと同じではない」のかを+1します。これは他の人によってあまり扱われていません。(複数のデッキがある場合でも、グローバル関数は同じカードの異なるインスタンスに対して異なる値を返します)。
アレクシス14

@supercatこれは、個別の質問またはチャットセッションに値しますが、私は現在どちらにも興味がありません:-((C#で)「インターフェイスを実装する構造は、同様に動作するクラスオブジェクトとは異なる動作をします」。考慮すべき他の動作の違いがありますが、以下のコードではAFAIK Interface iObj = (Interface)obj;の動作はiObjstructまたはのclass状態に影響されませんobj(ただし、その割り当てでボックス化されたコピーである場合を除きますstruct
Mark Hurd 14

150

OOPの背後にある基本的な考え方は、データと動作(そのデータに基づく)は不可分であり、クラスのオブジェクトの考え方によって結合されるということです。

クラスがOOPの基本概念であると仮定することはよくある間違いです。クラスは、カプセル化を実現する特に一般的な方法の1つにすぎません。しかし、それをスライドさせることができます。

一般的なカードゲームAPIがあるとします。クラスカードがあります。ここで、このカードクラスは、プレーヤーに対する可視性を決定する必要があります。

グッドヘブンNO。Bridgeをプレイしているときに、ダミーの手だけをダミーだけが知っている秘密からすべてに知られるように変更するとき、ハートの7つに尋ねますか?もちろん違います。それはカードの心配ではありません。

1つの方法は、CardクラスにisVisible(Player p)ブール値を設定することです。もう1つの方法は、プレーヤークラスにブールのisVisible(Card c)を設定することです。

両方とも恐ろしいです。どちらもしないでください。プレイヤーカードも、Bridgeのルールを実装する責任はありません!

代わりに、プレーヤーとカードのリストが表示されるカードを決定するビューポートクラスがある3番目のオプションを選択しました。

これまで「ビューポート」でカードをプレイしたことがないため、このクラスがカプセル化することになっているものがわかりません。私カードのいくつかのデッキ、何人かのプレーヤー、テーブル、およびホイルのコピーでカードをプレイしました。ビューポートが表すものはどれですか?

ただし、このアプローチは、可能なメンバー関数のCardクラスとPlayerクラスの両方を奪います。

良い!

カードの可視性以外のものに対してこれを行うと、すべての機能が他のクラスで実装されているため、純粋にデータを含むCardおよびPlayerクラスが残ります。これは明らかに、OOPの主要な考え方に反しています。

番号; OOPの基本的な考え方は、オブジェクトが懸念をカプセル化することです。システムでは、カードはあまり気にしません。どちらもプレイヤーではありません。 これは、世界を正確にモデリングしているためです。現実の世界では、プロパティは非常に単純なゲームに関連するカードです。ゲームのプレイを大きく変えることなく、カードの写真を1〜52の数字に置き換えることができます。ゲームのプレイを大きく変えることなく、4人を北、南、東、西というラベルのマネキンに置き換えることができます。 プレイヤーとカードは、カードゲームの世界で最もシンプルなものです。 ルールは複雑なものであるため、ルールを表すクラスは複雑な場所です。

プレイヤーの1人がAIである場合、その内部状態は非常に複雑になる可能性があります。しかし、そのAIはカードを見ることができるかどうかを判断しません。ルールはそれを決定します。

システムの設計方法は次のとおりです。

まず、複数のデッキを持つゲームがある場合、カードは驚くほど複雑です。あなたは質問を考慮しなければなりません:プレーヤーは同じランクの2枚のカードを区別できますか?プレイヤー1が7つのハートのうちの1つをプレイし、その後何かが起こり、プレイヤー2が7つのハートのうちの1つをプレイした場合、プレイヤー3はそれが同じ 7つのハートである判断できますか?これを慎重に検討してください。しかし、その懸念は別として、カードは非常にシンプルでなければなりません。それらは単なるデータです。

次に、プレーヤーの性質は何ですか?プレーヤー一連の表示可能なアクション消費しアクション生成ます

ルールオブジェクトは、これらすべてを調整するものです。ルールは目に見えるアクションのシーケンスを生成し、プレイヤーに通知します:

  • プレーヤー1、ハート10はプレーヤー3から渡されました。
  • プレイヤー2、プレイヤー3からプレイヤー1にカードが渡されました。

そして、プレーヤーにアクションを要求します。

  • プレーヤー1、何をしたいですか?
  • プレイヤーは言う:フロムを3倍にする。
  • プレーヤー1、それは違法行為です。なぜなら、3倍のfrompは防御できないギャンビットを生み出すからです。
  • プレーヤー1、何をしたいですか?
  • プレイヤー1は言う:スペードの女王を捨てる。
  • プレイヤー2、プレイヤー1はスペードのクイーンを破棄しました。

等々。

メカニズムをポリシーから分離します。ゲームのポリシーは、カードではなくポリシーオブジェクトにカプセル化する必要があります。カードは単なるメカニズムです。


41
@gnat:Alan Kayの対照的な意見は、「実際には「オブジェクト指向」という用語を作り上げました。C++を念頭に置いていなかったと言えます。」クラスのないOO言語があります。JavaScriptが思い浮かびます。
エリックリッパー14

19
@gnat:現在のJSはOOP言語の優れた例ではないことに同意しますが、クラスなしでOO言語を簡単に構築できることを示しています。EiffelとC ++のオブジェクト指向の基本単位はクラスであることに同意します。私が反対しているのは、クラスがオブジェクト指向の正弦波であるという概念です。OO の正弦波は、動作をカプセル化し、明確に定義されたパブリックインターフェイスを介して相互に通信するオブジェクトです。
エリックリッパー14

16
@EricLippertに同意します。クラスはオブジェクト指向の基本ではなく、継承も、メインストリームが言っていることには関係ありません。ただし、データ、動作、および責任のカプセル化は達成されます。ありますプロトタイプベースの言語 OOが、クラスレスですJavascriptを超えては。これらの概念を継承することに焦点を当てるのは特に間違いです。そうは言っても、クラスは振る舞いのカプセル化を整理する非常に有用な手段です。クラスをオブジェクトとして(そしてプロトタイプ言語では、その逆に)扱うことができると、線がぼやけます。
シュヴェルン14

6
このように考えてみてください。実際のカードは、実際にはどのような動作を示しますか?答えは「なし」だと思います。他のことはカードに作用します。カード自体は、現実の世界で、文字通り情報(クラブの4つ)のみであり、本質的な動作は一切ありません。その情報(別名「カード」)がどのように使用されるかは、「ルール」と「プレイヤー」とも呼ばれる何か/他の人に100%までです。同じカードは、任意の数の異なるプレーヤーによって、無限の(まあ、まったくそうではないかもしれません)さまざまな異なるゲームに使用できます。カードは単なるカードであり、所有するのはプロパティのみです。
クレイグ14

5
@Montagist:そ​​れでは、少し明確にしましょう。Cについて考えてみましょう。Cにはクラスがないことに同意すると思います。ただし、構造体は「クラス」であり、関数ポインタ型のフィールドを作成し、vtablesを作成し、vtablesを設定する「コンストラクター」と呼ばれるメソッドを作成して、いくつかの構造体が互いに「継承」できるようにすることができます。等々。あなたはできるエミュレート Cでクラスベースの継承をそして、あなたはできるエミュレート JSでそれを。しかし、そうすることは、まだ存在しない言語の上に何かを構築することを意味します。
エリックリッパー14

29

データと動作の結合がOOPの中心的な考え方であることは正しいですが、それ以外にもあります。たとえば、カプセル化:OOP /モジュラープログラミングにより、パブリックインターフェイスを実装の詳細から分離できます。OOPでは、これはデータが公にアクセスされることは決してなく、アクセサーを介してのみ使用されるべきであることを意味します。この定義により、メソッドのないオブジェクトは実際に役に立たなくなります。

アクセサー以外のメソッドを提供しないクラスは、本質的に複雑すぎる構造です。しかし、これは悪くありません。OOPは、内部の詳細を変更する柔軟性を提供しますが、構造体はそうではありません。たとえば、メンバーフィールドに値を保存する代わりに、毎回値を再計算できます。または、バッキングアルゴリズムが変更され、それに伴って追跡する必要がある状態が変更されます。

OOPには明らかな利点がありますが(特に単純な手続き型プログラミングよりも)、「純粋な」OOPを目指して努力するのは単純です。一部の問題はオブジェクト指向のアプローチにうまく対応せず、他のパラダイムにより簡単に解決されます。このような問題が発生した場合、劣ったアプローチを主張しないでください。

  • オブジェクト指向の方法でフィボナッチ数列を計算することを検討してください。私はそれを行うための健全な方法を考えることはできません。単純な構造化プログラミングは、この問題に対する最適なソリューションを提供します。

  • あなたのisVisible関係は、両方のクラスに属し、または実際にどちらも、あるいは:へのコンテキスト。振る舞いのない記録は、機能的または手続き型のプログラミング手法の典型であり、問​​題に最も適しているようです。何も悪いことはありません

    static boolean isVisible(Card c, Player p);
    

    そして、Cardそれ以上のメソッドranksuitアクセッサがないことで何も問題はありません。


11
@UMadはい、それはまさに私のポイントであり、それは何も悪いことではありません。手元の仕事に対して正しい<del> language </ del> パラダイムを使用します。(ところで、Smalltalk以外のほとんどの言語は純粋なオブジェクト指向ではありません。たとえば、Java、C#、C ++は命令型、構造化、手続き型、モジュール型、機能型、オブジェクト指向プログラミングをサポートしています。 :それらを使用できるように)
amon 14

1
フィボナッチを実行するための賢明なオブジェクト指向のfibonacci方法があり、のインスタンスでメソッドを呼び出しますinteger。一見小さな場所であっても、オブジェクト指向はカプセル化に関するものであるという点を強調したいと思います。整数は仕事をする方法を把握しましょう。後で実装を改善し、キャッシュを追加してパフォーマンスを改善できます。関数とは異なり、メソッドはデータを追跡するため、すべての呼び出し元は改善された実装の恩恵を受けます。後で任意の精度の整数が追加され、通常の整数のように透過的に扱うことができ、独自のパフォーマンス調整fibonacciメソッドを持つことができます。
シュヴェルン14

2
@Schwern Fibonacciは、abstract classのサブクラスである場合Sequence、シーケンスは任意の数値セットで使用され、シード、状態、キャッシング、および反復子の格納を担当します。
ジョージリース14

2
「純粋なOOPフィボナッチ」がオタクを狙うのにそれほど効果的であるとは思っていませんでした。エンターテインメント的価値はありましたが、これらのコメントでそれに関する循環的な議論を停止してください。さあ、みんな変化のために建設的なことをしましょう!
アモン14

3
フィボナッチを整数のメソッドにするのはばかげていると思いますが、まさにそれがOOPであると言えます。これは関数であり、関数のように扱う必要があります。
immibis 14

19

OOPの背後にある基本的な考え方は、データと動作(そのデータに基づく)は不可分であり、クラスのオブジェクトの考え方によって結合されるということです。オブジェクトには、それ(およびその他のデータ)で機能するデータとメソッドがあります。明らかに、OOPの原則により、単なるデータであるオブジェクト(C構造体など)はアンチパターンと見なされます。(...)これは明らかにOOPの主要な考え方に反しています。

かなりの数の不完全な前提に基づいているため、これは難しい質問です。

  1. OOPがコードを記述する唯一の有効な方法であるという考え。
  2. OOPは明確に定義された概念であるという考え。これは、OOPが何であるかについて同意できる2人を見つけるのが難しいほどの流行語になっています。
  3. OOPはデータと動作を束ねることに関する概念です。
  4. すべてが抽象的である/すべきであるという考え。

#1-3についてはあまり触れません。それぞれが独自の答えを生み出す可能性があり、意見に基づいた多くの議論を招くからです。しかし、私は「OOPはデータと動作を結びつけることだ」という考えが特に厄介であると感じています。#4につながるだけでなく、すべてがメソッドであるという考えにもつながります。

型を定義する操作と、その型を使用できる方法には違いがあります。ith要素を取得できることは、配列の概念にとって不可欠ですが、ソートは、1つの要素で選択できる多くのことの1つにすぎません。ソートは、「偶数要素のみを含む新しい配列を作成する」必要がある以上、メソッドである必要はありません。

OOPはオブジェクトの使用に関するものです。オブジェクトは抽象化を達成する1つの方法にすぎません。抽象化は、コード内の不必要な結合を回避するための手段であり、それ自体の目的ではありません。カードの概念がそのスイートとランクの値によってのみ定義されている場合、単純なタプルまたはレコードとして実装することは問題ありません。コードの他の部分が依存関係を形成する可能性のある非必須の詳細はありません。非表示にするものがない場合もあります。

目に見えるということは、カードの概念に不可欠ではない可能性が高いためisVisible、このCardタイプのメソッドを作成しません(半透明または不透明にできる特別なカードがない限り...)。Playerタイプのメソッドである必要がありますか?まあ、それはおそらくプレイヤーの決定的な品質ではありません。何らかのViewportタイプの一部である必要がありますか?繰り返しますが、それはビューポートの定義内容と、カードの可視性をチェックする概念がビューポートの定義に不可欠かどうかによって異なります。

ただのisVisible自由な機能であるべきである可能性は非常に高いです。


1
マインドレスドローンの代わりに常識のために+1。
右折14

私が読んだ行から、あなたがリンクしたエッセイは、私がしばらく持っていなかった1つのしっかりした読みのように見えます。
アーサーハヴリチェク14

@ArthurHavlicekコードサンプルで使用されている言語を理解していないと、追跡するのが難しくなりますが、私はそれが非常にわかりやすいと感じました。
ドーバル14

9

明らかに、OOPの原則により、単なるデータであるオブジェクト(C構造体など)はアンチパターンと見なされます。

いいえ、そうではありません。Plain-Old-Dataオブジェクトは完全に有効なパターンであり、プログラムの異なる領域間で永続化または通信する必要があるデータを扱うプログラムでは、それらを期待しています。

データ層はテーブルから読み込むときに完全なクラスをスプールするかもしれませんが、代わりに変換するプログラムの別の領域に渡すテーブルのフィールドを持つPODを返す一般的なデータライブラリである可能性があります具体的なクラスへのプレーヤーPOD 。PlayerPlayersPlayer

型付きまたは型なしのデータオブジェクトの使用は、プログラムで意味をなさない場合がありますが、アンチパターンになりません。理にかなっている場合は使用し、そうでない場合は使用しないでください。


5
あなたが言ったことに異議を唱えないでください、しかしこれは質問に全く答えません。そうは言っても、答えよりも質問のせいにします。
pdr 14

2
正確には、カードとドキュメントは現実の世界でさえ情報のコンテナに過ぎず、それを処理できない「パターン」は無視する必要があります。
ジェフ14

1
Plain-Old-Data objects are a perfectly valid pattern 私は彼らがそうではないとは言わなかった、私は彼らがアプリケーションの下半分全体を埋めるときそれが間違っていると言っている。
RokL 14

8

個人的には、ドメイン駆動設計はこの問題を明確にするのに役立つと思います。私が尋ねる質問は、カードゲームを人間にどのように説明すればよいのでしょうか?言い換えれば、私は何をモデリングしていますか?私がモデリングしているものに「ビューポート」という言葉とその動作に一致する概念が本当に含まれている場合、ビューポートオブジェクトを作成し、論理的にすべきことをさせます。

ただし、ゲームにビューポートの概念がない場合、それが必要だと思うのは、そうしないとコードが「間違っている」と感じるためです。ドメインモデルを追加することについて考え直します。

モデルという言葉は、何かの表現を構築していることを意味します。私は、あなたが表現しているものを超えた抽象的なものを表現するクラスを置くことには注意します。

ディスプレイとのインターフェイスが必要な場合は、コードの別の部分にビューポートの概念が必要になる可能性があることを追加して編集します。ただし、DDDの用語では、これはインフラストラクチャの問題であり、ドメインモデルの外側に存在します。


上記のリッパートの答えは、この概念のより良い例です。
RibaldEddie 14

5

私は通常、自己宣伝をしませんが、事実は私のブログで OOP設計の問題についてたくさん書いています。いくつかのページをまとめると、クラスから設計を開始すべきではありません。そこからインターフェースまたはAPIとシェイプコードを開始すると、意味のある抽象化を提供し、仕様に適合し、再利用不可能なコードで具体的なクラスを肥大化させることを回避する可能性が高くなります。

これがどのように適用されるかCard- Player問題:ViewPort抽象化の作成は、2つの独立したライブラリーであるCardと考える場合にPlayer意味Playerがあります(これはなしで使用されることを意味しますCard)。しかし、私はPlayerホールドを考える傾向がCardsありCollection<Card> getVisibleCards ()、それらへのアクセサーを提供する必要があります。これらの両方のソリューション(ViewPortおよび私のソリューション)は、理解可能なコード関係を作成するという点でisVisibleCardまたはのメソッドとして提供するよりも優れていPlayerます。

クラス外のソリューションは、はるかに優れていDocumentIdます。複雑なデータベースライブラリに依存する(基本的には整数にする)動機はほとんどありません。


あなたのブログが好きです。
RokL 14

3

目の前の質問が適切なレベルで回答されているかどうかはわかりません。私はフォーラムで賢明な人たちに、ここで質問の核心を積極的に考えるように促しました。

U Madは、OOPの彼の理解に従ってプログラミングすると、通常、多くのリーフノードがデータホルダーになり、上位レベルのAPIはほとんどの動作で構成されると考えている状況を提起しています。

このトピックは、isVisibleがCard vs Playerで定義されるかどうかにわずかに接していると思います。素朴ではありますが、単なる例です。

私はここで経験豊富な人にプッシュして、手元の問題を調べました。U Madが推し進めた良い質問があると思います。ルールと関係するロジックを独自のオブジェクトにプッシュすることを理解しています。しかし、私が理解しているように、質問は

  1. 実際に多くの機能を提供しない単純なデータホルダー構造(クラス/構造;この質問に関してはモデル化されたものは気にしません)を持つことは問題ありませんか?
  2. はいの場合、それらをモデル化する最良の方法または推奨される方法は何ですか?
  3. いいえの場合、このデータカウンターパーツをより高いAPIクラス(動作を含む)に組み込む方法

私の見解:

オブジェクト指向プログラミングでは、きちんと把握するのが難しい粒度の質問をしていると思います。私の小さな経験では、自分自身の動作を含まないエンティティをモデルに含めませんでした。必要な場合は、データと動作をカプセル化するという考えを持つクラスとは異なり、そのような抽象化を保持するように設計された構造体を使用した可能性があります。


3
問題は、質問(およびあなたの答え)が「一般に」物事を行う方法に関するものであることです。実際、私たち「一般に」物事を行うことはありません。私たちは常に特定のことを行います。特定の事柄が状況に対して正しいものであるかどうかを判断するために、特定の事柄を調べ、要件に対して測定する必要があります。
ジョンサンダース14

@JohnSaunders私はここであなたの知恵を認識し、ある程度同意しますが、問題に取り組む前に必要な単なる概念的なアプローチもあります。結局のところ、ここでの質問は、そう思われるほど自由端ではありません。これは、OOデザイナーがOOPの初期使用で直面する有効なOODの質問だと思います。あなたの意見は?結石が助けになれば、私たちはあなたの選択の例を構築することを議論することができます。
ハルシャ14

私は今35年以上学校を休んでいます。現実の世界では、「概念的アプローチ」にはほとんど価値がありません。この場合、マイヤーズよりも優れた教師であると経験しています。
ジョンサンダース14

データのクラスと動作の区別のクラスを本当に理解していません。オブジェクトを適切に抽象化した場合、区別はありません。Pointwith getX()関数を想像してください。属性の1つを取得していると想像できますが、ディスクまたはインターネットから読み取ることもできます。取得と設定は動作であり、まさにそれを行うクラスを持つことはまったく問題ありません。データベースはデータのみを取得および設定します
アーサーハヴリチェク14

@ArthurHavlicek:クラスをしないかを知ることは、それが何をするかを知ることと同じくらい便利です。コントラクトで、共有可能な不変のデータホルダーとしてのみ、または共有不可能な可変のデータホルダーとしてのみ動作するように指定することは有用です。
supercat 14

2

OOPの混乱の一般的な原因の1つは、多くのオブジェクトが状態の2つの側面をカプセル化しているという事実から生じています。オブジェクトの状態の議論では、後者の側面をしばしば無視します。なぜなら、オブジェクト参照が無差別であるフレームワークでは、参照が外部世界に公開されたことがあるオブジェクトについて何かを知る一般的な方法がないためです。

CardEntityカードのこれらの側面を個別のコンポーネントにカプセル化するオブジェクトを用意することは、おそらく役立つと思います。1つのコンポーネントは、カードのマーキングに関連します(たとえば、「Diamond King」または「Lava Blast。プレイヤーはAC-3で回避するか、2D6のダメージを受ける」)。位置などの状態の固有の側面(たとえば、デッキ、ジョーの手、またはラリーの前のテーブル)に関連する場合があります。3番目の人は、それを見ることができるかもしれません(おそらく誰も、おそらく1人のプレイヤー、またはおそらく多くのプレイヤー)。すべての同期を維持するために、カードが置かれる場所は単純なフィールドとしてではなく、CardSpaceオブジェクトとしてカプセル化されます。カードをスペースに移動するには、適切な場所への参照を与えますCardSpaceオブジェクト; その後、古いスペースから自分自身を削除し、新しいスペースに配置します)。

「Xを知っている人」とは別に「Xを知っている人」を明示的にカプセル化することで、多くの混乱を避けることができます。特に多くの関連付けがある場合、メモリリークを回避するために注意が必要な場合があります(たとえば、新しいカードが出現して古いカードが消えた場合、破棄されるべきカードが永続的なオブジェクトに永続的に取り付けられないようにする必要があります) )しかし、オブジェクトへの参照の存在がその状態の関連部分を形成する場合、オブジェクト自体がそのような情報を明示的にカプセル化することは完全に適切です(他のクラスに実際に管理する作業を委任する場合でも)。


0

ただし、このアプローチは、可能なメンバー関数のCardクラスとPlayerクラスの両方を奪います。

そして、それはどのように悪い/悪いアドバイスですか?

カードの例と同様の類推を使用するには、a Car、a Driverを検討し、Driverがを駆動できるかどうかを判断する必要がありますCar

OK、それであなたはあなたがあなたCarDriver正しい車の鍵を持っているかどうかを知りたくないと決めました、そしていくつかの未知の理由のためにあなたはあなたがクラスDriverについて知りたくないと決めましたCar(あなたは完全に肉体ではなかったあなたの元の質問にもこれがあります)。したがって、上記の質問の値を返すためのビジネスルールUtils持つメソッドを含む中間クラス、つまりクラスの行に沿ったものがあります。boolean

これでいいと思う。中間クラスは、車のキーを今すぐチェックするだけでよい場合がありますが、アルコールの影響下で運転手が有効な運転免許証を持っているかどうか、またはディストピアの将来、DNA生体認証をチェックするためにリファクタリングできます。カプセル化により、これらの3つのクラスが共存していても大きな問題はありません。

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