Tomando seus exemplos em PDF como ponto de partida, vejamos isso.
http://en.wikipedia.org/wiki/Single_responsibility_principle
O princípio da responsabilidade única sugere que um objeto deve ter um e apenas um objetivo. Mantenha isso em mente.
http://en.wikipedia.org/wiki/Separation_of_concerns
O princípio da separação de preocupações nos diz que as classes não devem ter funções sobrepostas.
Quando você olha para esses dois, eles sugerem que a lógica deve entrar em uma classe apenas se fizer sentido, somente se essa classe for responsável por fazer isso.
Agora, no seu exemplo em PDF, a pergunta é: quem é o responsável pela impressão? O que faz sentido?
Primeiro trecho de código:
Pdf pdf = new Pdf();
pdf.Print();
Isto não é bom. Um documento PDF não é impresso automaticamente. É impresso por ... ta da! .. uma impressora. Portanto, seu segundo trecho de código é muito melhor:
Pdf pdf = new Pdf();
PdfPrinter printer = new PdfPrinter();
printer.Print(pdf);
Isso faz sentido. Uma impressora PDF imprime um documento PDF. Melhor ainda, uma impressora não deve ser uma impressora PDF ou uma impressora fotográfica. Deve ser apenas uma impressora capaz de imprimir as coisas enviadas a ela da melhor maneira possível.
Pdf pdf = new Pdf();
Printer printer = new Printer();
printer.Print(pdf);
Então isso é simples. Coloque métodos onde eles fazem sentido. Obviamente, nem sempre é assim tão simples. Pegue as estatísticas do seu país, por exemplo:
Country m = new Country("Mexico");
double ratio = m.GetDebtToGDPRatio();
Sua preocupação é que possa haver n número de estatísticas e que elas não devam estar em uma classe Country. Isso é verdade. No entanto, se o seu modelo solicitar apenas essas estatísticas específicas, este exemplo de modelagem poderá realmente ser bom.
Nesse caso, você poderia dizer logicamente que um país deve poder calcular suas próprias estatísticas, específicas para o seu modelo e os requisitos em questão.
E é aí que reside: quais são os seus requisitos? Seus requisitos orientarão a maneira como você modela o mundo, o contexto em que esses requisitos devem ser atendidos.
Se você realmente tem um número infinito / variável de estatísticas, seu segundo exemplo faz mais sentido:
Country m = new Country("Mexico");
DebtStatistics ds = new DebtStatistics();
double usRatio = ds.GetDebtToGDPRatio(m);
Melhor ainda, tenha uma superclasse ou interface abstrata chamada Estatísticas que tome um país como parâmetro:
interface StatisticsCalculator // or a pure abstract class if doing C++
{
double getStatistics(Country country); // or a pure virtual function if in C++
}
A classe DebtToGDPRatioStatisticsCalculator implementa StatisticsCalculator ....
classe InfantMortalityStatisticsCalculator implementa StatisticsCalculator ...
E assim por diante. O que leva ao seguinte: generalização, delegação, abstração. A coleta estatística é delegada a instâncias específicas que generalizam uma abstração específica (uma API de coleta de estatísticas).
Não sei se isso responde 100% à sua pergunta. Afinal, não temos modelos infalíveis baseados em leis invioláveis (como o pessoal de EE). Tudo o que você pode fazer é colocar as coisas onde elas fazem sentido. E essa é uma decisão de engenharia que você precisa tomar. A melhor coisa a fazer é realmente se familiarizar com os princípios de OO (e bons princípios de modelagem de software em geral).