Prendendo i tuoi esempi PDF come punto di partenza, diamo un'occhiata a questo.
link
Il principio di responsabilità unica suggerisce che un oggetto dovrebbe avere un solo obiettivo. Tienilo a mente.
link
Il principio Separation of Concerns 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 tale classe è responsabile di farlo.
Ora, nel tuo esempio PDF, la domanda è: chi è il responsabile della stampa? Cosa ha senso?
Primo snippet di codice:
Pdf pdf = new Pdf();
pdf.Print();
Questo non va bene. Un documento PDF non si stampa da solo. Viene stampato da ... ta da! .. una stampante. Quindi il secondo snippet 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 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 le statistiche del tuo paese, ad esempio:
Country m = new Country("Mexico");
double ratio = m.GetDebtToGDPRatio();
La tua preoccupazione è che potrebbe esserci un n numero di statistiche e che non dovrebbero essere in una classe Paese. Questo è vero. Tuttavia, se il tuo modello richiede solo determinate statistiche, questo esempio di modellazione potrebbe effettivamente andare bene.
In questo caso, si potrebbe dire abbastanza logicamente che un paese dovrebbe essere in grado di calcolare le proprie statistiche, specifiche per il proprio modello e per i requisiti disponibili.
E qui sta la cosa: quali sono le tue esigenze? Le tue esigenze guideranno il tuo modo di modellare il mondo, il contesto in cui questi requisiti devono essere soddisfatti.
Se in effetti hai un numero di statistiche variabile / variabile, allora 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 o un'interfaccia astratta chiamata Statistiche che accetta 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++
}
classe DebtToGDPRatioStatisticsCalculator implementa StatisticsCalculator ....
classe InfantMortalityStatisticsCalculator implementa StatisticsCalculator ...
E così via e così via. Il che porta a quanto segue: generalizzazione, delega, astrazione. La raccolta statistica ottiene delegata a istanze specifiche che generalizza una astrazione specifica (un'API di raccolta statistiche).
Non so se questo risponde alla tua domanda al 100%. Dopotutto, non abbiamo modelli infallibili fondati su leggi inviolabili (come fanno gli EE). Tutto quello che puoi fare è mettere le cose se fossero sensate. E questa è una decisione ingegneristica che devi prendere. La cosa migliore da fare è conoscere i principi OO (e buoni principi di modellazione del software in generale).