Qual é a diferença entre o padrão de estratégia e injeção de dependência?

95

O padrão de estratégia e a injeção de dependência nos permitem definir / injetar objetos em tempo de execução. Qual é a diferença entre o padrão de estratégia e injeção de dependência?

Nero
fonte
O padrão de estratégia pode usar injeção de dependência
TechWisdom

Respostas:

107

DI e Strategy funcionam da mesma maneira, mas Strategy é usado para dependências mais refinadas e de curta duração.

Quando um objeto é configurado com uma Estratégia "fixa", por exemplo, quando o objeto é construído, a distinção entre Estratégia e DI fica borrada. Mas em um cenário de DI é mais incomum que as dependências dos objetos mudem durante suas vidas, enquanto isso não é incomum com Strategy.

Além disso, você pode passar estratégias como argumentos para métodos, enquanto o conceito relacionado de injeção de argumento de método não é difundido e é usado principalmente no contexto de teste automatizado apenas.

A estratégia foca na intenção e incentiva você a criar uma interface com diferentes implementações que obedecem ao mesmo contrato comportamental. DI é mais sobre apenas ter uma implementação de algum comportamento e fornecê-lo.

Com o DI você pode decompor seu programa por outras razões além de apenas ser capaz de trocar partes da implementação. Uma interface usada em DI com apenas uma implementação é muito comum. Uma "Estratégia" com apenas uma implementação concreta (nunca) não é um problema real, mas provavelmente está mais perto de DI.

eljenso
fonte
Uma interface usada em DI com apenas uma implementação é muito comum - então, o que é DI neste caso particular?
Kalpesh Soni
3
Esta citação basicamente explica tudo:in a DI scenario it is more unusual that the dependencies of objects change during their lifetimes, while this is not uncommon with Strategy
Sergey Telshevsky
Estratégia: As classes são projetadas para que possam ser configuradas com um algoritmo em tempo de execução. DI: Essas classes obtêm um algoritmo (um objeto Strategy) injetado em tempo de execução. Da memória de padrões de design GoF em w3sdesign.com .
GFranke
39

A diferença é o que eles estão tentando alcançar. O padrão Strategy é usado em situações em que você sabe que deseja trocar as implementações. Como exemplo, você pode querer formatar dados de maneiras diferentes - você pode usar o padrão de estratégia para trocar um formatador XML ou CSV, etc.

A injeção de dependência é diferente porque o usuário não está tentando alterar o comportamento do tempo de execução. Seguindo o exemplo acima, podemos estar criando um programa de exportação XML que usa um formatador XML. Em vez de estruturar o código assim:

public class DataExporter() {
  XMLFormatter formatter = new XMLFormatter();
}

você 'injetaria' o formatador no construtor:

public class DataExporter {
  IFormatter formatter = null;

  public DataExporter(IDataFormatter dataFormatter) {
    this.formatter = dataFormatter;
  }
}

DataExporter exporter = new DataExporter(new XMLFormatter());

Existem algumas justificativas para a injeção de dependência, mas a principal é para teste. Você pode ter um caso em que tenha um mecanismo de persistência de algum tipo (como um banco de dados). No entanto, pode ser difícil usar um banco de dados real quando você está executando testes repetidamente. Portanto, para seus casos de teste, você injetaria um banco de dados fictício, para não incorrer nessa sobrecarga.

Usando este exemplo, você pode ver a diferença: sempre planejamos usar uma estratégia de armazenamento de dados, e é aquela que passamos (a instância de banco de dados real). No entanto, no desenvolvimento e nos testes, queremos usar dependências diferentes, por isso injetamos diferentes concreções.

tsimon
fonte
28

Você pode usar o DI como um padrão de estratégia, então você pode trocar o algoritmo que é necessário para cada cliente, mas o DI pode ir além disso, pois é uma maneira de apenas desacoplar as partes de um aplicativo, que não fariam parte de o padrão de estratégia.

Seria arriscado dizer que o DI é apenas um padrão de estratégia renomeado, uma vez que começa a diluir o que o padrão de estratégia realmente serve, IMO.

James Black
fonte
2
Acho que entendo sua essência, mas não consigo colocá-la em palavras corretamente ... Então, você está dizendo que DI é mais um padrão de implementação, enquanto a estratégia é mais um padrão de design, e uma maneira de implementar a estratégia é por meio de DI?
Robert Gould
1
Parece uma boa maneira de colocar isso. DI é mais do que apenas um padrão de estratégia. Eu encontrei a mesma confusão com AOP, onde as pessoas pensam que é um padrão de fábrica. Eu acho que o DI pode implementar o padrão de estratégia, então sua reformulação pareceria fantástica. :)
James Black
14

Cara, injeção de dependência é um padrão mais geral, e é sobre dependência de abstrações, não concreções e faz parte de cada padrão, mas o padrão de estratégia é uma solução para um problema mais específico

esta é a definição da wikipedia:

DI:

A injeção de dependência (DI) na programação de computador orientada a objetos é um padrão de projeto com um princípio central de separar o comportamento da resolução de dependência. Em outras palavras: uma técnica para desacoplar componentes de software altamente dependentes.

Padrão de estratégia:

Na programação de computadores, o padrão de estratégia (também conhecido como padrão de política) é um padrão de design de software específico, por meio do qual algoritmos podem ser selecionados no tempo de execução.

O padrão de estratégia tem como objetivo fornecer um meio de definir uma família de algoritmos, encapsular cada um como um objeto e torná-los intercambiáveis. O padrão de estratégia permite que os algoritmos variem independentemente dos clientes que os utilizam.

Jahan
fonte
3
Gosto especialmente da parte "cara" na sua explicação. :-)
johey
7

Estratégias são coisas de nível superior usadas para mudar a forma como as coisas são computadas. Com a injeção de dependência, você pode alterar não apenas como as coisas são calculadas, mas também o que está lá.

Para mim, fica claro ao usar testes de unidade. Para a execução do código de produção, você tem todos os dados ocultos (ou seja, privados ou protegidos); ao passo que, com os testes de unidade, a maioria dos dados são públicos, então posso ver com os Asserts.


Exemplo de estratégia:

public class Cosine {
  private CalcStrategy strat;

  // Constructor - strategy passed in as a type of DI
  public Cosine(CalcStrategy s) {
    strat = s;
  }
}

public abstract class CalcStrategy {
  public double goFigure(double angle);
}

public class RadianStrategy extends CalcStrategy {
  public double goFigure(double angle) {
    return (...);
  }
}
public class DegreeStrategy extends CalcStrategy {
  public double goFigure(double angle) {
    return (...);
  }
}

Observe que não há dados públicos diferentes entre as estratégias. Nem há métodos diferentes. Ambas as estratégias compartilham as mesmas funções e assinaturas.


Agora, para a injeção de dependência:

public class Cosine {
  private Calc strat;

  // Constructor - Dependency Injection.
  public Cosine(Calc s) {
    strat = s;
  }
}

public class Calc {
  private int numPasses = 0;
  private double total = 0;
  private double intermediate = 0;

  public double goFigure(double angle) {
    return(...);
}

public class CalcTestDouble extends Calc {
  // NOTICE THE PUBLIC DATA.
  public int numPasses = 0;
  public double total = 0;
  public double intermediate = 0;
  public double goFigure(double angle) {
    return (...);
  }
}

Usar:

public CosineTest {

  @Test
  public void testGoFigure() {
    // Setup
    CalcTestDouble calc = new CalcTestDouble();
    Cosine instance = new Cosine(calc);

    // Exercise
    double actualAnswer = instance.goFigure(0.0);

    // Verify
    double tolerance = ...;
    double expectedAnswer = ...;
    assertEquals("GoFigure didn't work!", expectedAnswer,
         actualAnswer, tolerance);

    int expectedNumPasses = ...;
    assertEquals("GoFigure had wrong number passes!",
        expectedNumPasses, calc.numPasses);

    double expectedIntermediate = ...;
    assertEquals("GoFigure had wrong intermediate values!",
        expectedIntermediate, calc.intermediate, tolerance);
  }
}

Observe as 2 últimas verificações. Eles usaram os dados públicos no teste duplo que foi injetado na classe em teste. Não pude fazer isso com o código de produção por causa do princípio de ocultação de dados. Eu não queria ter um código de teste de propósito especial inserido no código de produção. Os dados públicos deveriam estar em uma classe diferente.

O duplo de teste foi injetado. Isso é diferente de apenas uma estratégia, pois afetou os dados e não apenas as funções.

Edward Ames
fonte
4

A injeção de dependência é um refinamento do padrão de estratégia que explicarei brevemente. Freqüentemente, é necessário escolher entre vários módulos alternativos em tempo de execução. Todos esses módulos implementam uma interface comum para que possam ser usados ​​de forma intercambiável. O objetivo do padrão de estratégia é remover o fardo de decidir sobre qual dos módulos usar (ou seja, qual "estratégia concreta" ou dependência) encapsulando o processo de tomada de decisão em um objeto separado que chamarei de objeto de estratégia.

A injeção de dependência refina o padrão de estratégia não apenas decidindo qual estratégia concreta usar, mas também criando uma instância da estratégia concreta e "injetando-a" de volta no módulo de chamada. Isso é útil mesmo se houver apenas uma única dependência, pois o conhecimento de como gerenciar (inicializar, etc.) a instância de estratégia concreta também pode estar oculta dentro do objeto de estratégia.

Andrew W. Phillips
fonte
1

Na verdade, a injeção de dependência também é muito semelhante ao padrão Bridge. Para mim (e de acordo com a definição), o padrão Bridge é para acomodar diferentes versões da implementação, enquanto o padrão Strategy é para uma lógica totalmente diferente. Mas o código de amostra parece estar usando DI. Então talvez DI seja apenas uma técnica ou implementação?

Calvin
fonte
0

Estratégia é uma arena para usar suas habilidades de injeção de dependência. Maneiras reais de implementar injeção de dependência são as seguintes: -

  1. Eventos
  2. Arquivos de configuração do mapa de unidade / estrutura (ou programaticamente) etc.
  3. Métodos de Extensão
  4. Padrão de fábrica abstrato
  5. Inversão de padrão de controle (usado por estratégia e Abstract Factory)

Porém, há uma coisa que faz com que a estratégia se destaque. Como você sabe, no Unity, quando o aplicativo é inicializado, todas as dependências são definidas e não podemos alterá-las mais. Mas a estratégia oferece suporte à mudança de dependência do tempo de execução. Mas NÓS temos que administrar / injetar a dependência, não a responsabilidade da Estratégia!

Na verdade, a estratégia não fala sobre injeção de dependência. Se necessário, isso pode ser feito através do Abstract Factory dentro de um padrão Strategy. Estratégia fala apenas em criar uma família de classes com interface e 'brincar' com ela. Durante o jogo, se descobrirmos que as classes estão em um nível diferente, temos que injetá-lo nós mesmos, mas não o trabalho de Estratégia.

Nuvens Azuis
fonte
0

Se considerarmos os princípios SOLID - usamos o padrão de estratégia para o princípio aberto e fechado e injeção de dependência para o princípio de inversão de dependência

Sumeet Patil
fonte
1
Não tenho certeza se entendi. Você poderia explicar como a Estratégia se relaciona com o princípio Aberto / Fechado e como o DI se relaciona com o DIP?
Adam Parkin