Беручи приклади 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 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%. Зрештою, у нас немає непогрішних моделей, заснованих на недоторканних законах (як це роблять люди з ЕЕ). Все, що ви можете зробити, - це зробити те, що вони мають сенс. І це інженерне рішення, яке потрібно прийняти. Найкраще зробити це - по-справжньому ознайомитись з принципами ОО (та принципами хорошого моделювання програмного забезпечення взагалі.)