Suponha que eu tenha um objeto personalizado, Student :
public class Student{
public int _id;
public String name;
public int age;
public float score;
}
E uma classe, Window , usada para mostrar informações de um Aluno :
public class Window{
public void showInfo(Student student);
}
Parece bastante normal, mas achei que o Window não é muito fácil de testar individualmente, porque ele precisa de um objeto Student real para chamar a função. Então, eu tento modificar showInfo para que ele não aceite um objeto Student diretamente:
public void showInfo(int _id, String name, int age, float score);
para que seja mais fácil testar o Window individualmente:
showInfo(123, "abc", 45, 6.7);
Mas eu achei que a versão modificada tem outros problemas:
Modificar Aluno (por exemplo: adicionar novas propriedades) requer a modificação da assinatura do método showInfo
Se o Student tivesse muitas propriedades, a assinatura do método seria muito longa.
Então, usando objetos personalizados como parâmetro ou aceite cada propriedade em objetos como parâmetro, qual é mais sustentável?
showInfo
requer uma String real, um float real e duas entradas reais. Como é fornecer umString
objeto real melhor do que fornecer umStudent
objeto real ?int
parâmetros. No site de chamada, não há confirmação de que você está passando na ordem correta. E se você trocarid
eage
, oufirstName
elastName
? Você está apresentando um ponto potencial de falha que pode ser muito difícil de detectar até que exploda na sua cara e você o adiciona em todos os sites de chamadas .showForm(bool, bool, bool, bool, int)
método - Eu amo aqueles ...Respostas:
Usar um objeto personalizado para agrupar parâmetros relacionados é, na verdade, um padrão recomendado. Como refatoração, é chamado de Introduzir Objeto de Parâmetro .
Seu problema está em outro lugar. Primeiro, o genérico não
Window
deve saber nada sobre o Student. Em vez disso, você deveStudentWindow
saber que apenas a exibição é exibidaStudents
. Segundo, não há absolutamente nenhum problema em criar umaStudent
instância para testarStudentWindow
, desdeStudent
que não contenha nenhuma lógica complexa que complicaria drasticamente o testeStudentWindow
. Se ele tiver essa lógica,Student
é preferível criar uma interface e zombar.fonte
Student
agrupamento faz sentido e é provável que surja em outras áreas do aplicativo.Student
, seria um Preserve Whole Objetoa.b.c
se o seu método for necessárioa
. Se o seu método chegar ao ponto em que você precisa ter aproximadamente mais de 4 parâmetros ou 2 níveis de acesso à propriedade, provavelmente precisará ser fatorado. Observe também que esta é uma diretriz - como todas as outras diretrizes, requer critério do usuário. Não o siga cegamente.Você diz que é
Mas você pode simplesmente criar um objeto de aluno para passar para sua janela:
Não parece muito mais complexo ligar.
fonte
Student
se refere a umUniversity
, que se refere a muitasFaculty
s eCampus
s, comProfessor
s eBuilding
s, nenhum dos quaisshowInfo
realmente usa, mas você não tenha definido qualquer interface que permite que os testes para "saber" que e fornecer apenas o estudante relevante dados, sem criar toda a organização. O exemploStudent
é um objeto de dados simples e, como você diz, os testes devem ter prazer em trabalhar com ele.Em termos leigos:
Editar:
Como @ Tom.Bowen89 afirma, não é muito mais complexo testar o método showInfo:
fonte
fonte
Steve McConnell, do Code Complete, abordou esse problema, discutindo os benefícios e as desvantagens de passar objetos para métodos em vez de usar propriedades.
Perdoe-me se eu entendi errado alguns detalhes, estou trabalhando de memória, pois já faz mais de um ano desde que tive acesso ao livro:
Ele chega à conclusão de que é melhor você não usar um objeto, enviando apenas as propriedades absolutamente necessárias para o método. O método não precisa saber nada sobre o objeto fora das propriedades que ele usará como parte de suas operações. Além disso, com o tempo, se o objeto for alterado, isso poderá ter consequências indesejadas no método usando o objeto.
Ele também abordou que, se você acabar com um método que aceita muitos argumentos diferentes, isso provavelmente é um sinal de que o método está fazendo muito e deve ser dividido em mais métodos menores.
No entanto, às vezes, às vezes, você realmente precisa de muitos parâmetros. O exemplo que ele dá seria de um método que constrói um endereço completo, usando muitas propriedades de endereço diferentes (embora isso possa ser contornado usando uma matriz de cadeias de caracteres quando você pensa sobre isso).
fonte
Student
neste caso). E é assim que o teste informa o design , abraçando completamente a resposta com mais votos, mantendo a integridade do design.É muito mais fácil escrever e ler testes se você passar o objeto inteiro:
Para comparação,
A linha pode ser escrita, se você passar valores separadamente, como:
onde a chamada de método real está enterrada em algum lugar como
Para chegar ao ponto, que você não pode colocar a chamada de método real no teste é um sinal de que sua API está incorreta.
fonte
Você deve passar o que faz sentido, algumas idéias:
Mais fácil de testar. Se o (s) objeto (s) precisam ser editados, o que requer menos refatoração? É útil reutilizar esta função para outros fins? Qual é a menor quantidade de informações que preciso fornecer a essa função para realizar seu objetivo? (Ao dividi-lo - pode permitir que você reutilize esse código - tenha cuidado para não cair no buraco do design de fazer essa função e depois gargalhar tudo para usar exclusivamente esse objeto.)
Todas essas regras de programação são apenas guias para você pensar na direção certa. Apenas não construa uma besta de código - se você não tiver certeza e precisar prosseguir, escolha uma direção / sua ou uma sugestão aqui, e se você chegar a um ponto em que pensa 'oh, eu deveria ter feito isso caminho '- você provavelmente pode voltar e refatorá-lo facilmente. (Por exemplo, se você tem turma de professor - ela precisa da mesma propriedade definida como Aluno e você muda sua função para aceitar qualquer objeto do formulário Pessoa)
Eu estaria mais inclinado a manter o objeto principal sendo transmitido - porque como eu codifico, ele explica mais facilmente o que essa função está fazendo.
fonte
Uma rota comum para contornar isso é inserir uma interface entre os dois processos.
Às vezes, isso fica um pouco confuso, mas as coisas ficam um pouco mais organizadas em Java se você usar uma classe interna.
Em seguida, você pode testar a
Window
classe dando a ela umHasInfo
objeto falso .Suspeito que este seja um exemplo do Padrão Decorador .
Adicionado
Parece haver alguma confusão causada pela simplicidade do código. Aqui está outro exemplo que pode demonstrar melhor a técnica.
fonte
Student
eString
aqui para o tipo de retorno é apenas para demonstração. Provavelmente haveria parâmetros adicionais paragetInfo
os quaisPane
desenhar se estiver desenhando. O conceito aqui é passar componentes funcionais como decoradores do objeto original .HasInfo
objetos.Student
sabe ser um.getInfo
retorno nulo, passe-oPane
para chamar, então a implementação (naStudent
classe) será acoplada repentinamente ao swing ou ao que você estiver usando. Se você fizer com que ela retorne alguma string e use 0 parâmetros, sua interface do usuário não saberá o que fazer com a string sem suposições mágicas e acoplamento implícito. Se vocêgetInfo
efetivamente retornar algum modelo de vista com propriedades relevantes, suaStudent
classe será novamente acoplada à lógica de apresentação. Eu não acho que qualquer uma dessas alternativas são desejáveisVocê já tem muitas respostas boas, mas aqui estão mais algumas sugestões que podem permitir que você veja uma solução alternativa:
Seu exemplo mostra um Aluno (claramente um objeto de modelo) sendo passado para uma Janela (aparentemente um objeto no nível da visualização). Um objeto intermediário do Controller ou Presenter pode ser benéfico se você ainda não tiver um, permitindo que você isole a interface do usuário do seu modelo. O controlador / apresentador deve fornecer uma interface que possa ser usada para substituí-la no teste da interface do usuário e deve usar interfaces para se referir aos objetos de modelo e exibir objetos para poder isolá-lo dos dois para teste. Pode ser necessário fornecer uma maneira abstrata de criar ou carregar esses objetos (por exemplo, objetos de fábrica, objetos de repositório ou similares).
Transferir partes relevantes dos objetos do modelo para um Objeto de Transferência de Dados é uma abordagem útil para fazer a interface quando o modelo se torna muito complexo.
Pode ser que seu Aluno viole o Princípio de Segregação de Interface. Nesse caso, pode ser benéfico dividi-lo em várias interfaces mais fáceis de trabalhar.
O Lazy Loading pode facilitar a construção de grandes gráficos de objetos.
fonte
Esta é realmente uma pergunta decente. O problema real aqui é o uso do termo genérico "objeto", que pode ser um pouco ambíguo.
Geralmente, em uma linguagem clássica de POO, o termo "objeto" passou a significar "instância de classe". As instâncias de classe podem ser bem pesadas - propriedades públicas e privadas (e as intermediárias), métodos, herança, dependências etc. Você realmente não gostaria de usar algo assim para simplesmente passar algumas propriedades.
Nesse caso, você está usando um objeto como um contêiner que simplesmente contém algumas primitivas. No C ++, objetos como esses eram conhecidos como
structs
(e ainda existem em linguagens como C #). As estruturas foram, de fato, projetadas exatamente para o uso que você fala - elas agrupavam objetos e primitivos relacionados quando tinham um relacionamento lógico.No entanto, nas linguagens modernas, não há realmente nenhuma diferença entre uma estrutura e uma classe quando você está escrevendo o código ; portanto, você pode usar um objeto. (Nos bastidores, no entanto, há algumas diferenças das quais você deve estar ciente - por exemplo, uma estrutura é um tipo de valor, não um tipo de referência.) Basicamente, desde que você mantenha seu objeto simples, será fácil para testar manualmente. Porém, linguagens e ferramentas modernas permitem mitigar bastante isso (via interfaces, estruturas de simulação, injeção de dependência etc.)
fonte
Student
) em modelos de exibição (StudentInfo
ouStudentInfoViewModel
etc.), mas pode não ser necessário.