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