Design: método de objeto vs método de classe separada que leva objeto como parâmetro?

14

Por exemplo, é melhor fazer:

Pdf pdf = new Pdf();
pdf.Print();

ou:

Pdf pdf = new Pdf();
PdfPrinter printer = new PdfPrinter();
printer.Print(pdf);

Outro exemplo:

Country m = new Country("Mexico");
double ratio = m.GetDebtToGDPRatio();

ou:

Country m = new Country("Mexico");
Country us = new Country("US");
DebtStatistics ds = new DebtStatistics();
double usRatio = ds.GetDebtToGDPRatio(us);
double mRatio = ds.GetDebtToGDPRatio(m);    

Minha preocupação no último exemplo é que existem estatísticas potencialmente infinitas (mas digamos apenas 10) que você talvez queira saber sobre um país; todos eles pertencem ao objeto país?

por exemplo

Country m = new Country("Mexico");
double ratio = m.GetGDPToMedianIncomeRatio();

Essas são proporções simples, mas vamos supor que as estatísticas sejam complicadas o suficiente para justificar um método.

Onde está a linha entre operações intrínsecas a um objeto x operações que podem ser executadas em um objeto, mas não fazem parte dele?

Do utilizador
fonte

Respostas:

16

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).

luis.espinal
fonte
1
+1 para a interface StatisticsCalculator (e uso subsequente do padrão de estratégia). E a resposta bem pensada e completa
edwardsmatt
3
tempo insuficiente para desconstruir tudo isso no momento, mas é preciso ressaltar que a classe Printer se tornará uma classe God ao longo do tempo, fortemente acoplada a todos os tipos de classes de documentos. Pdf.Print seria preferível - mas tudo depende de como você define a 'responsabilidade single' ;-)
Steven A. Lowe
@ Steve - o que você está propondo é uma ideia horrível (com o Pdf implementando print ()). Não reflete como a impressão é implementada na vida real. Todo sistema operacional e API de impressão que eu conheço fornece uma abstração Printer. Veja a lista de impressoras na sua máquina XP / Vista (ou em / var / spool ou equivalente em * nix.) Cada aplicativo serializa um objeto de documento em uma de suas impressoras. Não há impressora do Word, impressora de texto ou impressora de PDF. Existem apenas impressoras específicas para o dispositivo de impressão e não específicas para o tipo de documento.
Luis.espinal
2
+1 Gostei Estou refletindo sobre o que você disse .. @Steve e luis: Eu acho que a parte que falta no debate sobre objetos de Deus é um objeto genérico de impressora que deve aceitar alguns formatos padrão como ASCII ou bitmap (embora pdf provavelmente também é razoável) e deve ser da responsabilidade de uma terceira classe converter um tipo de documento específico (digamos, um documento do ms word) em um desses formatos padrão.
Utilizador
2
Parece-me que talvez um PDF seja capaz de renderizar-se em uma interface do Canvas ou em um objeto Image que possa ser processado por um objeto Printer.
Winston Ewert
4

Eu acho que nenhum é definitivamente melhor que o outro. O uso de pdf.Print () é mais rigoroso, mas ter uma classe PdfPrinter pode ser melhor se:

  • Você precisa gerenciar instâncias das impressoras
  • Existe uma grande variedade de opções e ações que explodiriam a complexidade do pdf.Print (...) (por exemplo, cancelamento de impressão, formatação extra, etc.)

Eu não ficaria preso a isso de outra maneira.

Kevin Hsu
fonte
uma resposta boa e prática; o tempo dirá como este precisa evoluir
Steven A. Lowe
1
A sugestão curta é examinar a lógica e os dados ao aplicar o SRP, a fim de decidir se nos arrependeremos de não dissociá-los mais cedo. O problema com o armazenamento de configurações por impressora na Pdfclasse é que elas não devem ser armazenadas juntas - Pdfé armazenado em um arquivo, mas as configurações por impressora devem ser armazenadas com o perfil de usuário / máquina.
rwong 20/05