Não declare interfaces para objetos imutáveis
[EDIT] Onde os objetos em questão representam objetos de transferência de dados (DTOs) ou dados antigos simples (PODs)
Essa é uma orientação razoável?
Até agora, muitas vezes eu criei interfaces para classes seladas que são imutáveis (os dados não podem ser alterados). Tentei tomar cuidado para não usar a interface em nenhum lugar em que me importe com a imutabilidade.
Infelizmente, a interface começa a invadir o código (e não é apenas com o meu código que estou preocupado). Você acaba passando por uma interface e, em seguida, querendo passar para algum código que realmente quer assumir que o que está sendo passado é imutável.
Devido a esse problema, estou pensando em nunca declarar interfaces para objetos imutáveis.
Isso pode ter ramificações em relação aos testes de unidade, mas, além disso, isso parece uma diretriz razoável?
Ou existe outro padrão que devo usar para evitar o problema da "interface de expansão" que estou vendo?
(Estou usando esses objetos imutáveis por várias razões: Principalmente para segurança do encadeamento, pois escrevo muito código multiencadeado; mas também porque significa que posso evitar fazer cópias defensivas de objetos passadas para os métodos. O código se torna muito mais simples Em muitos casos, quando você sabe que algo é imutável - o que não acontece se você recebeu uma interface.De fato, muitas vezes você não pode sequer fazer uma cópia defensiva de um objeto referenciado por uma interface se ela não fornecer uma interface. operação de clone ou qualquer maneira de serializá-la ...)
[EDITAR]
Para fornecer muito mais contexto para minhas razões para querer tornar objetos imutáveis, consulte esta postagem de blog de Eric Lippert:
http://blogs.msdn.com/b/ericlippert/archive/tags/immutability/
Devo também salientar que estou trabalhando com alguns conceitos de nível inferior aqui, como itens que estão sendo manipulados / passados em filas de tarefas com vários threads. Estes são essencialmente DTOs.
Joshua Bloch também recomenda o uso de objetos imutáveis em seu livro Effective Java .
Acompanhamento
Obrigado pelo feedback, todos. Decidi seguir em frente e usar essa diretriz para os DTOs e seus afins. Está funcionando bem até agora, mas faz apenas uma semana ... Ainda assim, está parecendo bom.
Há algumas outras questões relacionadas a isso sobre as quais quero perguntar; notavelmente algo que estou chamando de "Imutabilidade profunda ou superficial" (nomenclatura que roubei da clonagem profunda e superficial) - mas essa é uma pergunta para outra hora.
fonte
List<Number>
que pode conterInteger
,Float
,Long
,BigDecimal
, etc ... Todos os quais são imutáveis si.Respostas:
Na minha opinião, sua regra é boa (ou pelo menos não é ruim), mas apenas por causa da situação que você está descrevendo. Eu não diria que concordo com isso em todas as situações; portanto, do ponto de vista do meu pedante interior, devo dizer que sua regra é tecnicamente muito ampla.
Normalmente, você não definiria objetos imutáveis, a menos que eles sejam essencialmente usados como objetos de transferência de dados (DTO), o que significa que eles contêm propriedades de dados, mas pouca lógica e nenhuma dependência. Se for esse o caso, como parece aqui, eu diria que você pode usar os tipos concretos diretamente, em vez de interfaces.
Tenho certeza de que alguns puristas de teste de unidade discordarão, mas, na minha opinião, as classes de DTO podem ser excluídas com segurança dos requisitos de teste de unidade e injeção de dependência. Não há necessidade de usar uma fábrica para criar um DTO, pois ele não possui dependências. Se tudo criar os DTOs diretamente, conforme necessário, realmente não há como injetar um tipo diferente, portanto, não há necessidade de uma interface. E, como eles não contêm lógica, não há nada para testar a unidade. Mesmo que eles contenham alguma lógica, desde que não tenham dependências, deve ser trivial testar a lógica da unidade, se necessário.
Como tal, acho que a regra de que todas as classes de DTO não devem implementar uma interface, embora potencialmente desnecessária, não prejudicará o design do software. Como você tem esse requisito de que os dados precisam ser imutáveis e você não pode impor isso por meio de uma interface, eu diria que é totalmente legítimo estabelecer essa regra como um padrão de codificação.
O problema maior, no entanto, é a necessidade de impor estritamente uma camada limpa de DTO. Enquanto suas classes imutáveis sem interface existirem apenas na camada DTO e sua camada DTO permanecer livre de lógica e dependências, você estará seguro. Porém, se você começar a misturar suas camadas e tiver classes sem interface que funcionam como classes de camadas de negócios, acho que começará a ter muitos problemas.
fonte
Eu costumava fazer uma grande confusão sobre tornar meu código invulnerável ao uso indevido. Criei interfaces somente leitura para ocultar membros mutantes, adicionei muitas restrições às minhas assinaturas genéricas etc. etc. Aconteceu que na maioria das vezes eu tomava decisões de design porque não confiava em meus colegas de trabalho imaginários. "Talvez um dia eles contratem um novo funcionário e ele não saberá que a classe XYZ não pode atualizar o DTO ABC. Ah, não!" Outras vezes, eu estava focando no problema errado - ignorando a solução óbvia - não vendo a floresta através das árvores.
Eu nunca mais crio interfaces para meus DTOs. Trabalho com a suposição de que as pessoas que tocam no meu código (principalmente eu) sabem o que é permitido e o que faz sentido. Se eu continuo cometendo o mesmo erro estúpido, geralmente não tento fortalecer minhas interfaces. Agora passo a maior parte do tempo tentando entender por que continuo cometendo o mesmo erro. Geralmente é porque estou analisando demais algo ou perdendo um conceito-chave. Meu código ficou muito mais fácil de trabalhar desde que deixei de ser paranóico. Também acabo com menos "estruturas" que exigiam o conhecimento de alguém interno para trabalhar no sistema.
Minha conclusão foi encontrar a coisa mais simples que funciona. A complexidade adicional de criar interfaces seguras desperdiça tempo de desenvolvimento e complica o código simples. Preocupe-se com coisas assim quando você tiver 10.000 desenvolvedores usando suas bibliotecas. Acredite, isso o salvará de muita tensão desnecessária.
fonte
Parece uma boa orientação, mas por razões estranhas. Já tive vários lugares em que uma interface (ou classe base abstrata) fornece acesso uniforme a uma série de objetos imutáveis. Estratégias tendem a cair aqui. Objetos de estado tendem a cair aqui. Não acho que seja excessivo moldar uma interface para parecer imutável e documentá-la como tal em sua API.
Dito isto, as pessoas tendem a fazer uma interface excessiva de objetos Plain Old Data (doravante, POD) e até estruturas simples (geralmente imutáveis). Se o seu código não tem alternativas sãs para alguma estrutura fundamental, ele não precisa de uma interface. Não, o teste de unidade não é motivo suficiente para alterar seu design (zombar do acesso ao banco de dados não é o motivo pelo qual você fornece uma interface, é flexibilidade para mudanças futuras) - não é o fim do mundo se seus testes use essa estrutura fundamental básica como está.
fonte
Eu não acho que essa seja uma preocupação com a qual o implementador de acessores precisa se preocupar. Se
interface X
é para ser imutável, não é responsabilidade do implementador da interface garantir que eles implementem a interface de maneira imutável?No entanto, na minha opinião, não existe uma interface imutável - o contrato de código especificado por uma interface se aplica apenas aos métodos expostos de um objeto, e nada sobre as partes internas de um objeto.
É muito mais comum ver a imutabilidade implementada como um decorador do que como uma interface, mas a viabilidade dessa solução realmente depende da estrutura do seu objeto e da complexidade da sua implementação.
fonte
MutableObject
tivern
métodos que alteram estado em
métodos que retornam estado,ImmutableDecorator
podem continuar a expor os métodos que retornam state (m
) e, dependendo do ambiente, afirmar ou lança uma exceção quando um dos métodos mutáveis é chamado.No C #, um método que espera um objeto de um tipo "imutável" não deve ter um parâmetro de um tipo de interface, porque as interfaces C # não podem especificar o contrato de imutabilidade. Portanto, a diretriz que você está propondo não tem sentido em C # porque você não pode fazê-lo em primeiro lugar (é improvável que versões futuras dos idiomas o permitam).
Sua pergunta deriva de um sutil mal-entendido dos artigos de Eric Lippert sobre imutabilidade. Eric não definiu as interfaces
IStack<T>
eIQueue<T>
especificou contratos para pilhas e filas imutáveis. Eles não. Ele os definiu por conveniência. Essas interfaces lhe permitiram definir tipos diferentes para pilhas e filas vazias. Podemos propor um design e implementação diferentes de uma pilha imutável usando um único tipo sem exigir uma interface ou um tipo separado para representar a pilha vazia, mas o código resultante não parecerá tão limpo e seria um pouco menos eficiente.Agora, vamos nos ater ao design de Eric. Um método que requer uma pilha imutável deve ter um parâmetro do tipo
Stack<T>
em vez da interface geralIStack<T>
que representa o tipo de dados abstratos de uma pilha no sentido geral. Não é óbvio como fazer isso ao usar a pilha imutável de Eric e ele não discutiu isso em seus artigos, mas é possível. O problema está no tipo da pilha vazia. Você pode resolver esse problema, certificando-se de que nunca obtém uma pilha vazia. Isso pode ser garantido pressionando um valor fictício como o primeiro valor na pilha e nunca exibindo-o. Dessa forma, você pode transmitir com segurança os resultados dePush
ePop
paraStack<T>
.Ter
Stack<T>
implementosIStack<T>
pode ser útil. Você pode definir métodos que exijam uma pilha, qualquer pilha, não necessariamente uma pilha imutável. Estes métodos podem ter um parâmetro do tipoIStack<T>
. Isso permite que você passe pilhas imutáveis para ele. Idealmente,IStack<T>
faria parte da própria biblioteca padrão. No .NET, não existeIStack<T>
, mas existem outras interfaces padrão que a pilha imutável pode implementar, tornando o tipo mais útil.O design e implementação alternativos da pilha imutável a que me referi anteriormente usam uma interface chamada
IImmutableStack<T>
. Obviamente, inserir "Imutável" no nome da interface não torna imutável todo tipo de implementação. No entanto, nessa interface, o contrato de imutabilidade é apenas verbal. Um bom desenvolvedor deve respeitá-lo.Se você estiver desenvolvendo uma pequena biblioteca interna, poderá concordar com todos da equipe em honrar este contrato e usar
IImmutableStack<T>
como o tipo de parâmetros. Caso contrário, você não deve usar o tipo de interface.Gostaria de acrescentar, desde que você marcou a pergunta C #, que na especificação C # não existem DTOs e PODs. Portanto, descartá-los ou defini-los com precisão melhora a questão.
fonte