设计:对象方法与以对象为参数的单独类的方法?


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();

这些是简单的比率,但让我们假设统计数据足够复杂,可以采用一种方法。

对象固有的操作与可以在对象上执行但不属于对象的操作之间的界线在哪里?

Answers:


16

以您的PDF示例为起点,让我们来看一下。

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

单一责任原则建议一个对象应该只有一个目标。请记住这一点。

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

关注分离原则告诉我们,类不应具有重叠功能。

当您查看这两者时,他们建议逻辑仅在有意义的情况下才应放在一个类中,并且仅当该类负责这样做时才应使用。

现在,在您的PDF示例中,问题是,谁负责打印?有什么道理?

第一个代码段:

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

这个不好。PDF文档不会自行打印。它由... ta da!..打印机打印。因此,您的第二个代码段要好得多:

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类。那是真实的。但是,如果您的模型仅要求提供特定的统计信息,则此建模示例实际上可能很好。

在这种情况下,您可以在逻辑上说一个国家应该能够计算自己的统计信息,具体取决于您的模型和手头的要求。

事情就在这里:您有什么要求?您的需求将驱动您满足这些需求的建模环境的方式。

如果确实有大量/可变的统计信息,那么第二个例子更有意义:

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

更好的是,有一个抽象超类或称为Statistics的接口,该接口将国家作为参数:

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的人们一样。)您所能做的就是在有意义的地方放一些东西。这是您需要做出的工程决策。最好的办法是真正熟悉OO原则(以及一般的良好软件建模原则)。


1
+1为StatisticsCalculator接口(以及随后使用的策略模式)。经过深思熟虑的答案
edwardsmatt,2011年

3
目前没有足够的时间来彻底地对此进行解构,但是必须指出,随着时间的流逝,Printer类将成为God类,与所有文档类紧密相关。Pdf.Print是更好的选择-但是这完全取决于您如何定义“单一责任” ;-)
Steven A. Lowe

@Steve-您的建议是一个可怕的想法(让Pdf实现print())。它不能反映现实生活中的打印方式。我知道的每个操作系统和打印API都提供的抽象Printer。查看XP / Vista计算机中的打印机列表(或在/ var / spool下或* nix中的等效列表中)。每个应用程序都会将文档对象序列化到其打印机之一。没有Word打印机,文本打印机或PDF打印机。仅存在特定于打印设备的打印机,而不特定于文档类型的打印机。
luis.espinal 2011年

2
+1我喜欢它,我在想您的意思.. @Steve和luis:我认为关于God对象的辩论中遗失的部分是通用Printer对象应该接受ASCII或位图之类的一些标准格式(尽管pdf也可能是合理的),并且某些第3类负责将特定的文档类型(例如ms word文档)转换为这些标准格式之一。
用户

2
在我看来,PDF也许应该能够将自己呈现在Canvas接口或Image对象上,然后可以由Printer对象处理。
温斯顿·埃韦特

4

我认为,两者绝对不会比另一个更好。使用pdf.Print()更为严格,但是如果满足以下条件,则拥有PdfPrinter类可能会更好:

  • 您需要管理打印机的实例
  • pdf.Print(...)的复杂性(例如打印取消,额外的格式化等)有多种选择和操作。

否则我不会挂断电话。


一个好的,实用的答案;时间会告诉我们这将如何演变
史蒂文·洛

1
简短的建议是在应用SRP时同时考虑逻辑和数据,以决定我们是否后悔早将它们分离。在Pdf类中存储每台打印机设置的问题在于,不应将它们一起存储- Pdf存储在文件中,但每台打印机设置应与用户/机器配置文件一起存储。
rwong
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.