Prendendo i tuoi esempi PDF come punto di partenza, diamo un'occhiata a questo.
http://en.wikipedia.org/wiki/Single_responsibility_principle
Il principio di responsabilità singola suggerisce che un oggetto dovrebbe avere un unico obiettivo. Tienilo a mente.
http://en.wikipedia.org/wiki/Separation_of_concerns
Il principio di separazione delle preoccupazioni ci dice che le classi non dovrebbero avere funzioni sovrapposte.
Quando guardi questi due, suggeriscono che la logica dovrebbe andare in una classe solo se ha senso, solo se quella classe è responsabile di farlo.
Ora, nel tuo esempio PDF, la domanda è: chi è responsabile della stampa? Cosa ha senso?
Primo frammento di codice:
Pdf pdf = new Pdf();
pdf.Print();
Questo non è un bene. Un documento PDF non viene stampato da solo. Viene stampato da ... ta da! .. una stampante. Quindi il tuo secondo frammento di codice è molto meglio:
Pdf pdf = new Pdf();
PdfPrinter printer = new PdfPrinter();
printer.Print(pdf);
Questo ha senso. Una stampante PDF stampa un documento pdf. Meglio ancora, una stampante non dovrebbe essere una stampante PDF o una stampante fotografica. Dovrebbe essere solo una stampante in grado di stampare le cose inviate al meglio delle sue capacità.
Pdf pdf = new Pdf();
Printer printer = new Printer();
printer.Print(pdf);
Quindi è semplice. Metti i metodi dove hanno senso. Ovviamente, non è sempre così semplice. Prendi ad esempio le statistiche del tuo paese:
Country m = new Country("Mexico");
double ratio = m.GetDebtToGDPRatio();
La tua preoccupazione è che potrebbero esserci n numero di statistiche e che non dovrebbero essere in una classe Paese. Questo è vero. Tuttavia, se il tuo modello richiede solo quella particolare statistica, questo esempio di modellazione potrebbe effettivamente andare bene.
In questo caso, potresti dire abbastanza logicamente che un paese dovrebbe essere in grado di calcolare le proprie statistiche, specifiche per il tuo modello e i requisiti a portata di mano.
E qui sta la cosa: quali sono le tue esigenze? I tuoi requisiti guideranno il modo in cui modellerai il mondo, il contesto, in cui questi requisiti devono essere soddisfatti.
Se hai davvero un numero variabile / variabile di statistiche, il tuo secondo esempio ha più senso:
Country m = new Country("Mexico");
DebtStatistics ds = new DebtStatistics();
double usRatio = ds.GetDebtToGDPRatio(m);
Meglio ancora, avere una superclasse astratta o un'interfaccia chiamata Statistics che prende un paese come parametro:
interface StatisticsCalculator // or a pure abstract class if doing C++
{
double getStatistics(Country country); // or a pure virtual function if in C++
}
class DebtToGDPRatioStatisticsCalculator implementa StatisticsCalculator ....
class InfantMortalityStatisticsCalculator implementa StatisticsCalculator ...
E così via e così via. Il che porta a quanto segue: generalizzazione, delega, astrazione. La raccolta statistica viene delegata a istanze specifiche che generalizzano un'astrazione specifica (un'API di raccolta delle statistiche).
Non so se questo risponde alla tua domanda al 100%. Dopotutto, non abbiamo modelli infallibili basati su leggi inviolabili (come fanno le persone di EE). Tutto quello che puoi fare è mettere le cose nel senso che hanno senso. E questa è una decisione ingegneristica che devi prendere. La cosa migliore da fare è conoscere veramente i principi di OO (e buoni principi di modellizzazione del software in generale).