Injeção de Dependência: Devo criar uma classe Car para armazenar todas as suas partes?

10

Eu tenho muitos carros no meu aplicativo C ++, todos contidos em um RaceTrack.

Cada carro consiste em centenas de peças. Cada parte depende de outra ou duas partes.

Eu li muitas perguntas sobre SO no DI e no livro de Mark Seemann e parece que não devo definir uma classe Car apenas para conter as peças do carro, porque todas as peças do carro dependem uma da outra e essa sopa de peças é um carro. Estou certo?

Então, quando eu coloco todos os meus carros de corrida no RaceTrack, não haverá entidades de carros, mas muitas peças de carros, dependendo uma da outra na pista? Estou certo?

EDITAR:

Durante idades, eu estava programando aulas de carro porque era óbvio para mim que eu precisava de uma aula de carro se estiver programando uma lógica de carro. Mas com o DI não é tão óbvio para mim. Ainda me perguntando, é idiomático para o DI não criar uma classe Car se eu não tiver um papel definido para isso?

Quero dizer, não há problema em ter um SteeringWheel para dirigir, BoltsAndNuts nas rodas para a equipe de pit e todos os tipos de outras interfaces engraçadas sem ter uma instância que represente um carro como um todo?

Anton Petrov
fonte

Respostas:

24

De que serve um monte de peças de carros sentadas em uma pista de corrida fazendo alguém?

Se tudo o que a sua classe de carro faz é reter peças, é tão útil quanto uma bolsa de peças molhadas.

Uso

Como motorista, o que eu quero é algo que eu possa controlar. Isso responde quando peço velocidade. Que lida com os trilhos. Isso pode parar rapidamente.

O que eu quero é uma classe de carro que seja utilizável. Que posso dizer para fazer as coisas sem ter que pensar em como o carburador funciona. Eu só penso no pedal do acelerador. Como eles estão conectados não é algo que me preocupa. Isso é abstração.

Injeção de dependência

A injeção de dependência não tem nada a ver com isso. Quando estou dirigindo, não estou pensando em como foi construído. Contanto que funcione, eu não me importo com a forma como eles montam.

Não, o DI é o que permite à minha equipe trocar rapidamente meus pneus por pneus melhores quando começa a chover na pista. É bom poder fazer isso sem ter que entrar em um carro completamente diferente.

O DI é realmente sobre seguir um princípio: Separar o uso da construção.

Um carro que pode instalar pneus novos a 150 km / h pode parecer legal, mas acho que não vai ganhar nenhuma corrida com um pneu instalando uma engenhoca.

O DI é sobre a instalação de suas peças de maneira que a equipe de pit chegue a elas. Quando você usa newno mesmo local que programa o comportamento, é como se estivesse soldando o carburador no lugar. Certifique-se de que uma tocha de acetileno possa removê-la, mas considere o uso de porcas e parafusos primeiro.

Isso é o que DI é. Claro que não é tão fácil quanto newcriar algo assim que você perceber que deseja. Em vez disso, você deve escrever um código separado que saiba como montar seu carro. Mas com certeza torna mais fácil mudar as coisas mais tarde. E isso significa que você não precisa arrastar uma montadora de automóveis pela pista com você.

Construção

Algo, em algum lugar, precisa saber se os pneus são da Goodyear. Onde então colocar o código de construção do carro? Se não o carro, então a equipe do pit? Não. A pista? Não. Todos eles têm código de comportamento. Código que deve ser executado durante a corrida. A construção do carro deve acontecer antes da corrida em um local removido do código de comportamento. Mark Seemans chamou esse lugar de Raiz da Composição . A maioria das pessoas chama isso de principal.

É um padrão simples. Principalmente, construa o gráfico de objetos e chame um método comportamental em um objeto no gráfico de objetos. É isso aí. Este é o único lugar em que a construção e o comportamento devem estar juntos.

Isso não significa que a construção deva ser uma pilha de códigos processuais, todos dispostos sequencialmente em main. Você é livre para usar todas as ferramentas no idioma para fazer a construção. Só não misture isso com comportamento.

Fazer isso na linguagem e não usar alguma estrutura de DI ou contêiner de IoC é chamado de DI puro . Funciona muito bem. Tem há muito tempo. Costumávamos chamá-lo de passagem de referência .

Ferramentas DI

O que uma ferramenta DI compra para você são os detalhes da construção movidos para um idioma diferente (xml, json, qualquer que seja) que impõe a separação entre construção e comportamento. Se você não confia que seus colegas programadores não usem newquando não deveriam, isso pode ser atraente.

A desvantagem é que é tentador deixar os detalhes da ferramenta de DI se espalharem por toda a base de código. Às vezes, infectar a base de código com anotações proprietárias. Os exemplos que eles fornecem certamente encorajam isso. A ferramenta tende a se mover para o espaço da linguagem até que você não possa apenas anunciar o trabalho como um trabalho de programação Java, mas como um trabalho de programação Java / Spring.

Princípios de design

Durante idades, eu estava programando aulas de carro porque era óbvio para mim que eu precisava de uma aula de carro se estiver programando uma lógica de carro. Mas com o DI não é tão óbvio para mim. Ainda me perguntando, é idiomático para o DI não criar uma classe Car se eu não tiver um papel definido para isso?

Acho que você está aprendendo sobre abstração e mudando como decide que uma aula é necessária. Isso é bom. Mas isso não é sobre DI. O DI não ajuda a decidir se você precisa de uma aula de carro. O DI ajuda a impedir que o carro saiba e, portanto, se importa, se os pneus são da Goodyear. O DI ajuda a impedir que a pista saiba se os carros são fabricados no Japão.

Uma das questões mais fundamentais no design de software é "o que sabe sobre o quê?" Essa é a principal coisa que um diagrama UML mostra. Quando você inventa algo que está alcançando, é uma interface com a coisa concreta à qual está vinculado. O carro agora precisa saber que os pneus são da Goodyear. O que é meio chato se a Michelin quiser patrociná-lo.

Evitar fazer isso é chamado seguindo o princípio da inversão de dependência . Formalmente, um módulo de alto nível (como a classe de carro) não deve depender diretamente de módulos de baixo nível (como a classe GoodyearTire). Deve depender de uma abstração (como uma interface do Tyre).

Uma maneira de evitar isso é chamada Inversão de controle . Aqui a ênfase está na alteração do fluxo de controle. Os pneus movem o carro ou o carro move os pneus? Pensar nisso da maneira certa nos permite não acoplar estaticamente o carro e os pneus. O DI é uma maneira específica de seguir a Inversão do controle.

Nada disso informa se você precisa de uma aula de carro. Se você estiver programando a "lógica do carro", é bom que exista um lugar para mantê-lo, em vez de espalhá-lo por todos os lugares. Apenas não se iluda pensando que a lógica de construção do carro é a mesma que a lógica de comportamento do carro, de modo que tudo tem que viver no mesmo lugar. Mas se você não tem um rolo definido para um carro, também não precisa. Corra com motocicletas pela pista, se quiser.

Quero dizer, não há problema em ter um SteeringWheel para dirigir, BoltsAndNuts nas rodas para a equipe de pit e todos os tipos de outras interfaces engraçadas sem ter uma instância que represente um carro como um todo?

DI ou não DI, é bom ter uma instância que represente um carro como um todo, mas essa instância não é algo que eu queira saber diretamente se não for necessário. Dê-me uma abstração do carro para que eu não precise me preocupar se ele funciona com gás, diesel ou eletricidade quando o uso. Isso é apenas algo que eu deveria me preocupar quando eu estiver construindo ou mantendo. É bom se o código que usa o carro não precisa saber ou se importar com o funcionamento. "Eu não sei. Eu não quero saber."

candied_orange
fonte
Seria legal se você não usasse a metáfora do carro e da equipe de pit para uma pergunta que os use para representar o código. Mas ainda assim, uma boa resposta.
S. Tarık Çetin
6
E eu sempre pensei inversão de controle foi quando você orientar esquerda, mas o carro vira para a direita ...
mkrieger1
CandiedOrange, então nenhuma classe Car se não tiver interface significativa e responsabilidade bem definida? E não Car.h no meu projeto. E um colega olhando para o meu código e imaginando se é um aplicativo de gerenciamento de lojas eletrônicas ou garagem de peças para carros? :)
Anton Petrov
@AntonPetrov "se parece um pato e grasna como um pato, mas precisa de baterias, você provavelmente tem a abstração errada". Bons nomes são muito importantes e podem ser difíceis de pensar, mas devem ser alterados para refletir a responsabilidade bem definida e a interface significativa, para que não sejam uma surpresa. Se eu ler o nome, olhe para a interface e pense "é exatamente o que eu esperava", então você tem um bom nome.
candied_orange
15

parece que eu não deveria definir uma classe Car apenas para guardar as peças do carro, porque todas as peças do carro dependerão uma da outra e essa sopa de peças é um carro. Estou certo?

Não.

O ponto de injeção de dependência não é de todo para desencorajar dependências. Muito pelo contrário.

Injeção de dependência é sobre a pergunta: onde é que o carro começar suas peças a partir de ? Onde e como eles são criados, e como o carro obtém referências a eles?

Ingenuamente, o carro criaria suas próprias peças chamando new. Isso é fácil e direto, mas é muito inflexível. O carro precisa saber tudo o que é necessário para criar as peças e, se você quiser ter dois carros com peças diferentes, essa lógica também deve entrar no carro.

Com a injeção de dependência, toda essa lógica é retirada do carro e colocada em um componente de configuração, que cria todas as partes e liga as referências. As dependências totalmente configuradas são injetadas no carro - e umas nas outras.

Michael Borgwardt
fonte
1
Finalmente, uma explicação sadia do dependency injectionconceito.
Anatoly techtonik
1
Eu diria que o sistema mais comum para a abordagem "ingênua" não é que o carro chamaria de novo, mas que o usuário da classe de carro atribuiria suas peças às suas propriedades e, enquanto isso, a classe de carros estaria dentro um estado indeterminado. O DI fornece um meio de garantir mecanicamente a validade da classe.
Whatsisname
2
@whatsisname Eu posso facilmente usar o DI para criar objetos inválidos e parcialmente inicializados. A validação não é uma responsabilidade de DI. Nem está sendo imutável. O DI pode assumir a tarefa de construir objetos válidos e imutáveis. O DI não tem como impor que essas são as únicas maneiras pelas quais esses objetos são construídos e usados. Os objetos devem aplicar isso.
precisa saber é o seguinte
@CandiedOrange: para que você possa facilmente usar qualquer técnica para fazer algo meio idiota, e daí? Exigir dependências no construtor e torná-las imutáveis ​​não é uma bala de prata, mas é uma maneira muito útil de evitar muitas situações ruins comuns.
Whatsisname
0

Então, quando eu coloco todos os meus carros de corrida no RaceTrack, não haverá entidades de carros, mas muitas peças de carros, dependendo uma da outra na pista? Estou certo?

Bem. Sim e não. Uma entidade automóvel ainda é válida para ter. E um carro é mais do que a soma de suas partes. Você precisa de um motorista também (por enquanto). Muitas vezes, os carros de corrida também têm nomes, sem mencionar a marca e o modelo do carro.

Sim, os carros são basicamente um contêiner de peças, mas é essa embalagem e cooperação de peças que compõem algo maior: um carro.

Então, sério, não. Um carro não é apenas uma sacola aleatória de metal e plástico. É uma máquina.

A injeção de dependência não é um exagero nesse caso. Outra coisa tem que construir o carro, que seria uma classe de fábrica ou um objeto Builder. O carro não deveria saber como se construir.

Greg Burghardt
fonte
2
Você pode ter DI sem fábricas ou objetos de construção. Parâmetros simples do construtor é um método válido que funciona para muitas circunstâncias.
Whatsisname