Usar injeção de dependência para objetos de dados?

11

Estou apenas aprendendo sobre injeção de dependência e estou preso a alguma coisa. Injeção de Dependência recomenda o envio de classes dependentes através do construtor, mas estou me perguntando se isso é necessário para objetos de dados. Como a Unidade de Testabilidade é um dos principais benefícios da DI, um objeto de dados, que apenas armazena dados e nunca procedimentos, é testado em unidade, tornando a DI uma camada desnecessária de complexidade, ou ainda ajuda a mostrar dependências mesmo com objetos de dados?

Class DO{
    DO(){
        DataObject2List = new List<DO2>();
    }

    public string Field1;
    public string Field2;
    public List<DO2> DataObject2List;
}

Class DO2{
    public DateTime Date;
    public double Value;
}
sooprise
fonte
O que exatamente você quer dizer com "objeto de dados"? Esse não é um termo padrão. Você está falando de um DTO ou está se referindo a qualquer classe sem métodos (como uma parte particularmente chata de um modelo de domínio)? Há uma enorme diferença entre os dois.
Aaronaught
Claro, quero dizer apenas uma classe sem métodos, uma classe que apenas armazena dados. Se Data Object não é o termo correto para isso, existe um ou é apenas chamado de classe sem métodos?
sooprise
@sooprise Esta é uma boa pergunta. Bem pensado.
Matthew Rodatus
@sooprise Talvez minha resposta esteja fora da base. Onde você colocará os métodos CRUD? Em uma classe de acesso a dados separada que pegaria os objetos de dados e os persistiria em uma tabela de banco de dados? Ou seja, DataAccess.Create (<DataObject>)?
Matthew Rodatus
1
@ Matthew, isso seria um objeto de acesso a dados - se é de fato o que o OP está falando, isso não está claro. As implementações modernas tendem a se afastar desse padrão de qualquer maneira, contando com repositórios e / ou unidades de trabalho.
Aaronaught

Respostas:

7

Gostaria de sugerir um esclarecimento sobre algumas das terminologias que você está usando aqui, especificamente "dependência" e "injeção de dependência".

Dependência:

Uma "dependência" é normalmente um objeto complexo que executa algumas funcionalidades das quais outra classe pode precisar depender. Alguns exemplos clássicos seriam um criador de logs ou um acessador de banco de dados ou algum componente que processe uma parte específica da lógica de negócios.

Um objeto apenas de dados, como um DTO ou um objeto de valor, normalmente não é chamado de "dependência", uma vez que eles não executam alguma função necessária.

Depois de olhar dessa maneira, o que você está fazendo no seu exemplo ( compondo o DOobjeto com uma lista de D02objetos por meio do construtor) não deve ser considerado "injeção de dependência". É apenas definir uma propriedade. Depende de você fornecê-lo no construtor ou de alguma outra maneira, mas simplesmente transmiti-lo pelo construtor não torna a injeção de dependência.

Injeção de dependência:

Se sua DO2classe estivesse realmente fornecendo alguma funcionalidade adicional de que a DOclasse precisa, seria realmente uma dependência. Nesse caso, a classe dependente DO,, deve depender de uma interface (como ILogger ou IDataAccessor) e, por sua vez, contar com o código de chamada para fornecer essa interface (em outras palavras, para "injetar" na DOinstância).

Injetar a dependência dessa maneira torna o DOobjeto mais flexível, pois cada contexto diferente pode fornecer sua própria implementação da interface para o DOobjeto. (Pense em teste de unidade.)

Eric King
fonte
7

Eu farei o meu melhor para resolver a confusão na pergunta.

Primeiro de tudo, "Objeto de Dados" não é um termo significativo. Se a única característica definidora desse objeto é que ele não possui métodos, ele não deveria existir . Um objeto sem comportamento útil deve caber em pelo menos uma das seguintes subcategorias:

  • Objetos de valor ou "registros" não têm identidade. Eles devem ser tipos de valor , com semântica de cópia na referência, assumindo que o ambiente o suporte. Como essas são estruturas fixas, um VO deve sempre ser um tipo primitivo ou uma sequência fixa de primitivos. Portanto, um VO não deve ter nenhuma dependência ou associação; qualquer construtor não padrão existiria apenas com o objetivo de inicializar o valor, ou seja, porque ele não pode ser expresso como literal.

  • Os objetos de transferência de dados geralmente são confundidos por engano com objetos de valor. DTOs fazer têm identidades, ou pelo menos eles podem . O único objetivo de um DTO é facilitar o fluxo de informações de um domínio para outro. Eles nunca têm "dependências". Eles podem ter associações (isto é, a uma matriz ou coleção), mas a maioria das pessoas prefere deixá-las planas. Basicamente, eles são análogos às linhas na saída de uma consulta ao banco de dados; são objetos transitórios que geralmente precisam ser persistidos ou serializados e, portanto, não podem fazer referência a nenhum tipo abstrato, pois isso os tornaria inutilizáveis.

  • Por fim, o Data Access Objects fornece um invólucro ou fachada a um banco de dados de algum tipo. Obviamente, eles têm dependências - eles dependem da conexão com o banco de dados e / ou dos componentes de persistência. No entanto, suas dependências são quase sempre gerenciadas externamente e totalmente invisíveis para os chamadores. No padrão Active Record , é a estrutura que gerencia tudo através da configuração; nos modelos DAO mais antigos (antigos para os padrões atuais), você só podia construí-los através do contêiner. Se eu visse um desses com injeção de construtor, ficaria muito, muito preocupado.

Você também pode estar pensando em um objeto de entidade ou "objeto de negócios" e, nesse caso , deseja oferecer suporte à injeção de dependência, mas não da maneira que pensa ou pelos motivos que pensa. Não é para o benefício do código do usuário , é para o benefício de um gerente de entidade ou ORM, que injeta silenciosamente um proxy que ele intercepta para fazer coisas sofisticadas, como compreensão de consultas ou carregamento lento.

Nestas, você geralmente não fornece um construtor para injeção; em vez disso, você só precisa tornar a propriedade virtual e usar um tipo abstrato (por exemplo, em IList<T>vez de List<T>). O resto acontece nos bastidores, e ninguém é o mais sábio.

Então, apesar de tudo, eu diria que um padrão de DI visível sendo aplicado a um "objeto de dados" é desnecessário e provavelmente até uma bandeira vermelha; mas, em grande parte, isso ocorre porque a própria existência do objeto é uma bandeira vermelha, exceto no caso em que está sendo especificamente utilizado para representar dados de um banco de dados. Em quase todos os outros casos, é um cheiro de código, geralmente o começo de um Modelo de Domínio Anêmico ou, no mínimo, de um Poltergeist .

Reiterar:

  1. Não crie "objetos de dados".
  2. Se você deve criar um "objeto de dados", verifique se ele tem uma finalidade claramente definida . Esse objetivo informará se o DI é apropriado ou não. É impossível tomar decisões de design significativas sobre um objeto que não deveria existir em primeiro lugar.

Fin.

Aaronaught
fonte
0

No seu exemplo, DOnão possui dependências funcionais (basicamente porque não faz nada). Ele depende do tipo concreto DO2, portanto, você pode querer introduzir uma interface para abstrair DO2, para que o consumidor possa implementar sua própria implementação concreta da classe filho.

Realmente, que dependência você injetaria aqui?

Scott Whitlock
fonte
Por outra pergunta sua que eu respondi, acho que a pergunta se refere a um objeto de dados com operações CRUD incorporadas. Como / onde a dependência do banco de dados é injetada no objeto de dados? Nos métodos? No construtor? De alguma outra maneira? A suposição é que a dependência não deve ser ocultada no corpo dos métodos - isso desativa a separação da dependência do banco de dados do Data Object, para que o Data Object possa ser testado por unidade.
Matthew Rodatus
@ Matthew Rodatus - Entendo. Sim, nesse caso, você tem duas opções: injetar o serviço de persistência ou criar outra classe chamada DOPersisterque sabe persistir DOe deixá-lo como um objeto estritamente somente de dados (melhor na minha opinião). No último caso, DOPersisterseria injetado com a dependência do banco de dados.
Scott Whitlock
Depois de reler sua pergunta, tenho menos certeza. Minha análise pode estar errada. Ele disse em sua pergunta que seu DO não teria nenhum procedimento. Isso significaria que a persistência NÃO acontece no DO. Nesse caso, sua resposta está correta - não há dependências para injetar.
Matthew Rodatus
0

Como esse é um objeto de dados na camada de acesso a dados, ele deve depender diretamente de um serviço de banco de dados. Você pode especificar um DatabaseService para o construtor:

DataObject dataObject = new DataObject(new DatabaseService());
dataObject.Update();

Mas, a injeção não precisa estar no construtor. Como alternativa, você pode fornecer a dependência por meio de cada método CRUD. Prefiro esse método ao anterior, porque seu Data Object não precisa saber onde ele persistirá até que você realmente precise persistir.

DataObject dataObject = new DataObject();
dataObject.Update(new DatabaseService());

Você definitivamente não deseja ocultar a construção nos métodos CRUD!

public void Update()
{
    // DON'T DO THIS!
    using (DatabaseService dbService = new DatabaseService())
    {
        ...
    }
}

Uma opção alternativa seria construir o DatabaseService por meio de um método de classe substituível.

public void Update()
{
    // GetDatabaseService() is protected virtual, so in unit testing
    // you can subclass the Data Object and return your own
    // MockDatabaseService.
    using (DatabaseService dbService = GetDatabaseService())
    {
        ...
    }
}

Uma alternativa final é usar um ServiceLocator no estilo singleton. Embora eu não goste desta opção, ela é testável por unidade.

public void Update()
{
    // The ServiceLocator would not be a real singleton. It would have a setter
    // property so that unit tests can swap it out with a mock implementation
    // for unit tests.
    using (DatabaseService dbService = ServiceLocator.GetDatabaseService())
    {
        ...
    }
}
Matthew Rodatus
fonte