O padrão de estratégia funciona bem para evitar enormes construções de if ... else e facilita a adição ou substituição de funcionalidades. No entanto, ainda deixa uma falha na minha opinião. Parece que em todas as implementações ainda precisa haver uma construção de ramificação. Pode ser uma fábrica ou um arquivo de dados. Como exemplo, considere um sistema de pedidos.
Fábrica:
// All of these classes implement OrderStrategy
switch (orderType) {
case NEW_ORDER: return new NewOrder();
case CANCELLATION: return new Cancellation();
case RETURN: return new Return();
}
O código depois disso não precisa se preocupar, e há apenas um lugar para adicionar um novo tipo de pedido agora, mas esta seção do código ainda não é extensível. Colocá-lo em um arquivo de dados ajuda a facilitar a leitura (discutível, eu sei):
<strategies>
<order type="NEW_ORDER">com.company.NewOrder</order>
<order type="CANCELLATION">com.company.Cancellation</order>
<order type="RETURN">com.company.Return</order>
</strategies>
Mas isso ainda adiciona código padrão para processar o código de dados concedido, mais facilmente testável por unidade e relativamente estável, mas com complexidade adicional.
Além disso, esse tipo de construção não faz um teste de integração bem. Cada estratégia individual pode ser mais fácil de testar agora, mas cada nova estratégia adicionada é uma complexidade adicional a ser testada. É menos do que você faria se não tivesse usado o padrão, mas ainda está lá.
Existe uma maneira de implementar o padrão de estratégia que mitiga essa complexidade? Ou isso é tão simples quanto parece, e tentar ir além adicionaria outra camada de abstração com pouco ou nenhum benefício?
fonte
eval
... pode não funcionar em Java, mas talvez em outras linguagens?Respostas:
Claro que não. Mesmo se você usar um contêiner de IoC, precisará ter condições em algum lugar, decidindo qual implementação concreta injetar. Essa é a natureza do padrão de estratégia.
Realmente não vejo por que as pessoas pensam que isso é um problema. Existem declarações em alguns livros, como a Refatoração de Fowler , de que se você vir uma opção / caixa ou uma cadeia de if / elses no meio de outro código, considere esse cheiro e procure movê-lo para seu próprio método. Se o código em cada caso for mais do que uma linha, talvez duas, considere tornar esse método um Método de Fábrica, retornando Estratégias.
Algumas pessoas entenderam que isso significa que uma troca / caso é ruim. Este não é o caso. Mas deve residir por conta própria, se possível.
fonte
Sim , usando um hashmap / dicionário no qual todas as implementações da Estratégia são registradas. O método de fábrica se tornará algo como
Toda implementação de estratégia deve registrar a fábrica com seu orderType e algumas informações sobre como criar uma classe.
Você pode usar o construtor estático para registro, se o seu idioma suportar isso.
O método register nada mais é do que adicionar um novo valor ao hashmap:
[update 2012-05-04]
Esta solução é muito mais complexa do que a "solução de switch" original, que eu preferiria na maioria das vezes.
No entanto, em um ambiente em que as estratégias mudam com frequência (ou seja, cálculo de preços dependendo do cliente, tempo, ....), essa solução de hashmap combinada com um IoC-Container pode ser uma boa solução.
fonte
factory.register(NEW_ORDER, NewOrder.class);
limpador, ou menos uma violação do OCP, do que as linhas decase NEW_ORDER: return new NewOrder();
?"Estratégia" é ter que escolher entre algoritmos alternativos pelo menos uma vez , não menos. Em algum lugar do seu programa, alguém precisa tomar uma decisão - talvez o usuário ou o seu programa. Se você usar IoC, reflexão, avaliador de arquivo de dados ou construção de switch / case para implementar isso, não mudará a situação.
fonte
O padrão de estratégia é usado quando você especifica comportamentos possíveis e é melhor usado ao designar um manipulador na inicialização. A especificação de qual instância da estratégia usar pode ser feita por meio de um mediador, seu contêiner de IoC padrão, alguma fábrica como você descreve ou simplesmente usando a correta com base no contexto (já que muitas vezes a estratégia é fornecida como parte de um uso mais amplo da classe que o contém).
Para esse tipo de comportamento em que diferentes métodos precisam ser chamados com base em dados, sugiro usar as construções de polimorfismo fornecidas pela linguagem em questão; não é o padrão de estratégia.
fonte
Ao conversar com outros desenvolvedores onde trabalho, encontrei outra solução interessante - específica para Java, mas tenho certeza de que a idéia funciona em outras linguagens. Construa uma enumeração (ou um mapa, não importa muito qual) usando as referências de classe e instanciar usando reflexão.
Isso reduz drasticamente o código de fábrica:
(Por favor, ignore o tratamento incorreto de erros - código de exemplo :))
Não é o mais flexível, pois uma compilação ainda é necessária ... mas reduz a alteração de código para uma linha. Eu gosto que os dados sejam separados do código de fábrica. Você pode até fazer coisas como definir parâmetros usando classes / interfaces abstratas para fornecer um método comum ou criando anotações em tempo de compilação para forçar assinaturas de construtor específicas.
fonte
A extensibilidade pode ser aprimorada fazendo com que cada classe defina seu próprio tipo de pedido. Em seguida, sua fábrica seleciona a que combina.
Por exemplo:
fonte
Eu acho que deveria haver um limite em quantos ramos são aceitáveis.
Por exemplo, se eu tiver mais de oito casos dentro da minha instrução switch, reavaliar meu código e procurar o que posso re-fatorar. Muitas vezes, acho que certos casos podem ser agrupados em uma fábrica separada. Minha suposição aqui é que existe uma fábrica que constrói estratégias.
De qualquer forma, você não pode evitar isso e em algum lugar terá que verificar o estado ou o tipo do objeto com o qual está trabalhando. Você criará uma estratégia para isso. Boa e velha separação de preocupações.
fonte