デザイン:オブジェクトメソッドと、オブジェクトをパラメーターとして取る別のクラスのメソッド


14

たとえば、行う方が良いですか:

Pdf pdf = new Pdf();
pdf.Print();

または:

Pdf pdf = new Pdf();
PdfPrinter printer = new PdfPrinter();
printer.Print(pdf);

もう一つの例:

Country m = new Country("Mexico");
double ratio = m.GetDebtToGDPRatio();

または:

Country m = new Country("Mexico");
Country us = new Country("US");
DebtStatistics ds = new DebtStatistics();
double usRatio = ds.GetDebtToGDPRatio(us);
double mRatio = ds.GetDebtToGDPRatio(m);    

最後の例での私の懸念は、国について知りたいと思うかもしれない潜在的に無限の統計があります(しかし、たった10個とさえ言えます)。それらはすべて国オブジェクトに属しますか?

例えば

Country m = new Country("Mexico");
double ratio = m.GetGDPToMedianIncomeRatio();

これらは単純な比率ですが、統計がメソッドを保証するほど複雑であると仮定しましょう。

オブジェクトに固有の操作と、オブジェクトで実行できるがその一部ではない操作との間のその境界線はどこにあるのでしょうか?

回答:


16

出発点としてPDFの例を取り上げて、これを見てみましょう。

http://en.wikipedia.org/wiki/Single_responsibility_principle

単一責任の原則は、オブジェクトにはただ1つの目的を持たせることを提案しています。これを覚えておいてください。

http://en.wikipedia.org/wiki/Separation_of_concerns

懸念の分離の原則は、クラスに重複する機能があってはならないことを示しています。

これら2つを見ると、理にかなっている場合にのみ、そのクラスがそれを行う責任がある場合にのみ、ロジックがクラスに入るべきであると示唆しています。

さて、あなたのPDFの例では、誰が印刷を担当しているのでしょうか?何が理にかなっていますか?

最初のコードスニペット:

Pdf pdf = new Pdf();
pdf.Print();

これは良くない。PDFドキュメントはそれ自体を印刷しません。それは... ta da!...プリンターによって印刷されます。したがって、2番目のコードスニペットははるかに優れています。

Pdf pdf = new Pdf();
PdfPrinter printer = new PdfPrinter();
printer.Print(pdf);

意味あり。PDFプリンターはPDFドキュメントを印刷します。さらに良いことに、プリンターはPDFプリンターや写真プリンターであってはなりません。それは、送信されたものを最高の能力で印刷できるプリンターでなければなりません。

Pdf pdf = new Pdf();
Printer printer = new Printer();
printer.Print(pdf);

それは簡単です。メソッドを意味のある場所に配置します。明らかに、それは必ずしもそれほど単純ではありません。あなたの国の統計を例にとります:

Country m = new Country("Mexico");
double ratio = m.GetDebtToGDPRatio();

あなたの懸念は、統計がn個あるかもしれず、それらがCountryクラスにあるべきではないということです。それは本当です。ただし、モデルがその特定の統計のみを必要とする場合、このモデリングの例は実際には問題ありません。

この場合、国はモデルと手元の要件に固有の独自の統計を計算できるべきであるとかなり論理的に言うことができます。

そしてそこにあるのは、あなたの要件は何ですか?要件は、これらの要件が満たされる世界、コンテキストをモデル化する方法を推進します。

実際に多数/可変の統計情報がある場合、2番目の例はより意味があります。

Country m = new Country("Mexico");
DebtStatistics ds = new DebtStatistics();
double usRatio = ds.GetDebtToGDPRatio(m);

さらに良いことに、国をパラメーターとする統計と呼ばれる抽象スーパークラスまたはインターフェースを用意します。

interface StatisticsCalculator // or a pure abstract class if doing C++
{
   double getStatistics(Country country); // or a pure virtual function if in C++
}

クラスDebtToGDPRatioStatisticsCalculatorはStatisticsCalculatorを実装します....

クラスInfantMortalityStatisticsCalculatorはStatisticsCalculatorを実装します...

などなど。これは、一般化、委任、抽象化につながります。統計収集は、特定の抽象化(統計収集API)を一般化する特定のインスタンスに委任されます。

これがあなたの質問に100%答えるかどうかはわかりません。結局、(EEの人々のように)不可侵の法律に基づいた絶対的なモデルはありません。あなたができることは、それらが理にかなっていることを置くことだけです。そして、それはあなたがする必要がある工学上の決定です。最善の方法は、オブジェクト指向の原則(および一般的な優れたソフトウェアモデリングの原則)に精通することです。


1
StatisticsCalculatorインターフェイスの+1(およびその後の戦略パターンの使用)。そして徹底的に
熟考

3
現時点ではこれを完全に分解するには時間が足りませんが、Printerクラスは時間の経過とともに神のクラスになり、あらゆる種類のドキュメントクラスと密接に結びつくことに注意する必要があります。Pdf.Printが望ましいでしょう-しかし、それはすべて「単一責任」を定義する方法に依存します;
スティーブンA.ロウ

@Steve-あなたが提案しているのは恐ろしいアイデアです(Pdfにprint()を実装させる)。印刷が実際にどのように実装されているかは反映されていません。私が知っているすべてのオペレーティングシステムと印刷APIは、の抽象化を提供しますPrinter。XP / Vistaマシン(または/ var / spoolまたは* nixで同等のもの)のプリンターリストを見てください。各アプリケーションは、ドキュメントオブジェクトをプリンターの1つにシリアル化します。Wordプリンター、テキストプリンター、PDFプリンターはありません。ドキュメントタイプに固有ではなく、印刷デバイスに固有のプリンターだけがあります。
luis.espinal

2
+1私はそれが好きです。あなたが言ったことを熟考しています。おそらく合理的です)、特定のドキュメントタイプ(msワードドキュメントなど)をこれらの標準形式のいずれかに変換するのは、いくつかの3番目のクラスの責任です。
ユーザー

2
おそらくPDFは、CanvasインターフェイスまたはImageオブジェクトにレンダリングされ、Printerオブジェクトで処理できるはずです。
ウィンストンイーバート

4

私はどちらも間違いなく他より優れていると思います。pdf.Print()の使用はより厳密ですが、次の場合はPdfPrinterクラスを使用する方が適切です。

  • プリンターのインスタンスを管理する必要があります
  • pdf.Print(...)の複雑さを吹き飛ばす幅広いオプションとアクションがあります(例:印刷キャンセル、余分なフォーマットなど)

そうでなければ、私はそれに夢中になりません。


適切で実用的な答え。時間がこれをどのように進化させる必要があるかを説明します
スティーブンA.ロウ

1
短い提案は、SRPを適用する際にロジックとデータの両方を調べて、それらをすぐに分離しないことを後悔しないかどうかを判断することです。プリンターごとの設定をPdfクラスに保存する際の問題は、それらが一緒に保存されることを想定していないことPdfです。ファイルに保存されますが、プリンターごとの設定はユーザー/マシンプロファイルと共に保存する必要があります。
rwong
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.