Дизайн: метод Object против метода отдельного класса, который принимает Object в качестве параметра?

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

Принцип единой ответственности предполагает, что объект должен иметь одну и только одну цель. Имейте это в виду.

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

Принцип разделения интересов говорит нам, что классы не должны иметь перекрывающихся функций.

Когда вы смотрите на эти два, они предполагают, что логика должна идти в классе, только если это имеет смысл, только если этот класс отвечает за это.

Теперь, в вашем примере с PDF, вопрос в том, кто отвечает за печать? Что имеет смысл?

Первый фрагмент кода:

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

Это не хорошо. Документ PDF не печатает сам. Он печатается ... та да! .. принтером. Таким образом, ваш второй фрагмент кода намного лучше:

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

Еще лучше иметь абстрактный суперкласс или интерфейс под названием Статистика, который принимает страну в качестве параметра:

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). Все, что вы можете сделать, - это положить вещи, которые имеют смысл. И это инженерное решение, которое вам нужно принять. Лучше всего по-настоящему познакомиться с принципами ОО (и с хорошими принципами моделирования программного обеспечения в целом).

luis.espinal
источник
1
+1 за интерфейс Статистического калькулятора (и последующее использование шаблона стратегии). И тщательно продуманный ответ
edwardsmatt
3
В настоящее время недостаточно времени, чтобы полностью разобраться в этом, но следует отметить, что класс Printer со временем станет классом God, тесно связанным со всеми видами классов документов. Pdf.Print будет предпочтительнее - но все зависит от того, как вы определяете «единую ответственность» ;-)
Стивен А. Лоу,
@ Steve - то, что вы предлагаете, является ужасной идеей (Pdf реализует print ()). Это не отражает, как печать реализована в реальной жизни. Каждая операционная система и API печати, которые я знаю, предоставляют абстракцию для Printer. Посмотрите на список принтеров на вашем компьютере с XP / Vista (или в / var / spool или эквивалентном в * nix). Каждое приложение сериализует объект документа на один из своих принтеров. Нет принтера Word, текстового принтера или принтера PDF. Есть только принтеры, специфичные для печатающего устройства и не специфичные для типа документа.
luis.espinal
2
+1 Мне это нравится. Я размышляю над тем, что вы сказали. @Steve and luis: я думаю, что пропущенная часть дискуссии об объектах Бога - это общий объект Printer, который должен принимать несколько стандартных форматов, таких как ASCII или растровое изображение (хотя pdf это, вероятно, также разумно), и ответственность за преобразование определенного типа документа (скажем, документа в формате MS Word) в один из этих стандартных форматов должна нести какой-то третий класс.
Пользователь
2
Мне кажется, что, возможно, PDF должен быть в состоянии визуализировать себя либо в интерфейс Canvas, либо в объект Image, который затем может быть обработан объектом Printer.
Уинстон Эверт
4

Я думаю, что ни один не определенно лучше, чем другой. Использование pdf.Print () более уместно, но лучше иметь класс PdfPrinter, если:

  • Вам нужно управлять экземплярами принтеров
  • Существует широкий спектр опций и действий, которые могут устранить сложность pdf.Print (...) (например, отмена печати, дополнительное форматирование и т. Д.)

В противном случае я бы не стал зацикливаться на этом.

Кевин Хсу
источник
хороший, практичный ответ; время покажет, как это должно развиваться
Стивен А. Лоу
1
Короткое предложение заключается в том, чтобы при применении SRP рассмотреть как логику, так и данные , чтобы решить, будем ли мы сожалеть о том, что не разъединили их раньше. Проблема с сохранением настроек для каждого принтера в Pdfклассе заключается в том, что они не должны храниться вместе - они Pdfхранятся в файле, но настройки для каждого принтера должны храниться вместе с профилем пользователя / машины.
Rwong