Quando você deve e não deve usar a palavra-chave 'new'?

14

Eu assisti a uma apresentação do Google Tech Talk sobre Testes Unitários , ministrada por Misko Hevery, e ele disse para evitar usando a palavra-chave new no código de lógica de negócios.

Eu escrevi um programa, e acabei usando a palavra-chave new aqui e ali, mas eles eram principalmente para instanciar objetos que armazenam dados (ou seja, eles não tinham nenhuma função ou método).

Eu estou pensando, eu fiz algo errado quando usei a nova palavra-chave para o meu programa. E onde podemos quebrar essa 'regra'?

    
por skizeey 24.10.2011 / 20:02
fonte

5 respostas

16

Isso é mais orientação do que regras rígidas e rápidas.

Ao usar "novo" em seu código de produção, você está acoplando sua turma a seus colaboradores. Se alguém quiser usar algum outro colaborador, por exemplo, algum tipo de colaborador simulado para testes de unidade, eles não podem - porque o colaborador é criado em sua lógica de negócios.

Naturalmente, alguém precisa criar esses novos objetos, mas geralmente é melhor deixar um desses dois locais: uma estrutura de injeção de dependência como Spring, ou em qualquer classe que instancie sua classe de lógica de negócios, injetada pelo construtor.

Claro, você pode levar isso longe demais. Se você quiser retornar um novo ArrayList, então provavelmente você está OK - especialmente se isso for uma lista imutável.

A principal questão que você deve estar se perguntando é "a principal responsabilidade deste código é criar objetos desse tipo, ou isso é apenas um detalhe de implementação que eu poderia razoavelmente mover para outro lugar?"

    
por 24.10.2011 / 20:10
fonte
5

O ponto crucial desta questão é no final: Eu estou pensando, eu fiz algo errado quando usei a nova palavra-chave para o meu programa. E onde podemos quebrar essa 'regra'?

Se você é capaz de escrever testes de unidade eficazes para o seu código, então você não fez nada errado. Se o uso de new dificultou ou impossibilitou a unidade testar seu código, então você deve reavaliar seu uso de novo. Você poderia estender essa análise para a interação com outras classes, mas a capacidade de escrever testes de unidade sólidos geralmente é um proxy bom o suficiente.

    
por 24.10.2011 / 20:25
fonte
5

Em suma, sempre que você usar "novo", você estará acoplando firmemente a classe que contém esse código ao objeto que está sendo criado; a fim de instanciar um desses objetos, a classe que faz a instanciação deve saber sobre a classe concreta que está sendo instanciada. Assim, ao usar "novo", você deve considerar se a classe na qual você está colocando a instanciação é um "bom" lugar para esse conhecimento residir, e você está disposto a fazer mudanças nesta área se a forma da objeto sendo instanciado deveriam mudar.

O acoplamento apertado, que é um objeto tendo conhecimento de outra classe concreta, nem sempre deve ser evitado; em algum nível, algo, EM ALGUM LUGAR, tem que saber como criar esse objeto, mesmo que tudo o mais lide com o objeto ao receber uma cópia dele de algum outro lugar. No entanto, quando a classe que está sendo criada é alterada, qualquer classe que conheça a implementação concreta dessa classe deve ser atualizada para lidar corretamente com as alterações dessa classe.

A pergunta que você deve sempre fazer é: "Ter essa turma sabendo como criar essa outra turma se tornará uma desvantagem ao manter o aplicativo?" Ambas as principais metodologias de design (SOLID e GRASP) geralmente respondem "sim", por razões sutilmente diferentes. No entanto, eles são apenas metodologias, e ambos têm a extrema limitação de não serem formulados com base no conhecimento de seu programa exclusivo. Como tal, eles só podem errar do lado da precaução, e assumir que qualquer ponto de acoplamento apertado irá eventualmente causar um problema relacionado a fazer mudanças em um ou ambos os lados deste ponto. Você deve tomar uma decisão final sabendo três coisas; a melhor prática teórica (que é para acoplar tudo de maneira frouxa porque qualquer coisa pode mudar); o custo de implementar as melhores práticas teóricas (que podem incluir várias novas camadas de abstração que facilitarão um tipo de mudança enquanto atrapalham outra); e a probabilidade do mundo real de que o tipo de mudança que você está antecipando será necessário.

Algumas diretrizes gerais:

  • Evite o acoplamento entre bibliotecas de código compiladas. A interface entre as DLLs (ou um EXE e suas DLLs) é o principal local onde o acoplamento rígido apresentará uma desvantagem. Se você fizer uma alteração em uma classe A na DLL X e a classe B no EXE principal conhecer a classe A, será necessário recompilar e liberar os dois binários. Dentro de um único acoplamento binário, mais apertado é geralmente mais permissível, porque o binário inteiro deve ser reconstruído para qualquer alteração de qualquer maneira. Às vezes, ter que recompilar vários binários é inevitável, mas você deve estruturar seu código para poder evitá-lo sempre que possível, especialmente em situações em que a largura de banda é escassa (como implantar aplicativos móveis; de empurrar todo o programa).

  • Evite o acoplamento entre os principais "centros lógicos" do seu programa. Você pode pensar em um programa bem estruturado como consistindo em fatias horizontais e verticais. Fatias horizontais podem ser camadas de aplicativos tradicionais, como interface do usuário, controlador, domínio, DAO, dados; fatias verticais podem ser definidas para janelas ou visualizações individuais ou para "histórias de usuário" individuais (como criar um novo registro de algum tipo básico). Ao fazer uma chamada que se move para cima, para baixo, para a esquerda ou para a direita em um sistema bem estruturado, você deve resumir a chamada em geral. Por exemplo, quando a validação precisa recuperar dados, ela não deve ter acesso ao banco de dados diretamente, mas deve fazer uma chamada para uma interface para recuperação de dados, que é apoiada pelo objeto real que sabe como fazer isso. Quando algum controle de interface do usuário precisa executar lógica avançada envolvendo outra janela, deve abstrair o acionamento dessa lógica por meio de um evento e / ou retorno de chamada; ele não precisa saber o que será feito como resultado, permitindo que você altere o que será feito sem alterar o controle que o aciona.

  • Em qualquer caso, considere quão fácil ou difícil será uma mudança, e qual será a provável mudança. Se um objeto que você está criando é usado apenas de um lugar, e você não prevê essa mudança, então o acoplamento apertado é geralmente mais permissível, e pode até ser melhor nessa situação soltar o acoplamento. O acoplamento flexível requer abstração, que é uma camada extra que impede a alteração em objetos dependentes quando a implementação de uma dependência deve ser alterada. No entanto, se a própria interface tiver que mudar (adicionar uma nova chamada de método ou adicionar um parâmetro a uma chamada de método existente), uma interface aumentará a quantidade de trabalho necessária para fazer a alteração. É preciso ponderar a probabilidade de diferentes tipos de alteração impactarem o design, e será inviável ou até mesmo impossível criar um sistema fechado para todos os tipos de alteração.

por 25.10.2011 / 00:26
fonte
3

Objetos que apenas armazenam dados, ou DTOs (objetos de transferência de dados) são bons, crie-os durante todo o dia. Onde você quer evitar que new esteja em classes que executam ações, você quer que as classes consumidoras programem em vez disso na interface , onde elas podem invocar essa lógica e consumi-la, mas não se responsabilizarão realmente criando as instâncias das classes que contêm a lógica. Essas classes de execução de ação ou contendo lógica são dependências. A conversa de Misko é direcionada para você injetar essas dependências e deixar que outra entidade seja responsável por realmente criá-las.

    
por 24.10.2011 / 20:08
fonte
2

Outros já mencionaram esse ponto, mas queriam citar isso do Código Limpo do Tio Bob (Bob Martin) porque isso poderia facilitar a compreensão do conceito:

"Um poderoso mecanismo para separar construção de uso é Injeção de Dependência (DI), a aplicação de Inversão de Controle (IoC) ao gerenciamento de dependências. responsabilidades secundárias de um objeto para outros objetos que são dedicados à finalidade, apoiando assim o Princípio da Responsabilidade Única .No contexto do gerenciamento de dependências, um objeto não deve assumir a responsabilidade de instanciar dependências em si. deve passar essa responsabilidade para outro mecanismo "autoritário", invertendo assim o controle. Como a configuração é uma preocupação global, esse mecanismo autoritativo geralmente será a rotina "principal" ou um contêiner de propósito especial. "

Eu acho que é sempre uma boa ideia seguir esta regra. Outros mencionaram o mais importante IMO - > dissocia sua classe de suas dependências (colaboradores). A segunda razão é que ela torna suas classes menores e mais rápidas, mais fáceis de entender e até reutilizar em outros contextos.

    
por 24.10.2011 / 20:39
fonte