Padrão de estratégia detalhada do padrão de design

Autor: Liu Wenhui

O padrão de estratégia é um padrão de comportamento amplamente utilizado. A ideia central é encapsular o algoritmo e delegá-lo a diferentes objetos para gerenciamento. Este artigo se concentrará no padrão de estratégia a ser compartilhado.

I. Visão geral

Se quisermos alcançar manutenibilidade e escalabilidade durante o desenvolvimento de software, precisamos reutilizar o código o máximo possível e reduzir o acoplamento do código. O padrão de projeto é um método que pode melhorar a reusabilidade, manutenibilidade e escalabilidade do código. legibilidade.

Os 23 padrões de design bem conhecidos podem ser divididos em três categorias: padrões criacionais, padrões estruturais e padrões comportamentais. Entre eles, o padrão comportamental pode ser usado para descrever como múltiplas classes e múltiplos objetos no programa cooperam para completar tarefas complexas, envolvendo a atribuição de responsabilidades entre diferentes objetos e a abstração de algoritmos. O padrão de estratégia é um padrão de comportamento amplamente utilizado.Este artigo enfocará o padrão de estratégia para aprender e compartilhar.

2. Conceitos básicos

A ideia central do padrão de estratégia é encapsular o algoritmo e delegá-lo a diferentes objetos para gerenciamento. Dessa forma, podemos definir uma série de algoritmos, encapsular cada algoritmo em uma série de classes de estratégias concretas com uma interface comum, para que possam ser substituídos de forma flexível e o algoritmo possa ser alterado sem afetar o cliente. Ao mesmo tempo, o modo de estratégia apenas encapsula algoritmos (incluindo adição e exclusão), mas não decide quando usar qual algoritmo, e a escolha do algoritmo é determinada pelo cliente.

Por exemplo, existem muitas estratégias de viagem que podemos escolher quando viajamos: bicicletas, carros, trens e aviões. Cada estratégia de viagem tem seu próprio método de uso. Desde que possamos chegar ao destino, podemos mudar várias estratégias em vai. Outro exemplo é quando vamos ao shopping, haverá muitas atividades promocionais no shopping: desconto total, abatimento, etc. Esses métodos de promoção são essencialmente algoritmos, e o próprio algoritmo também é uma estratégia, que pode ser substituída a qualquer momento Para o mesmo produto, hoje Ganhe 50 de desconto ao gastar 500 e ganhe 100 cupons amanhã ao gastar 300. Essas estratégias também são intercambiáveis.

Então, como devemos usar o padrão de estratégia? O que se segue dará uma introdução conceitual ao padrão de estratégia a partir de dois níveis de estrutura e etapas de uso.

2.1 Estrutura

O padrão de estratégia contém três categorias, que são categoria de estratégia abstrata, categoria de estratégia concreta e categoria de ambiente.Eles são responsáveis ​​por concluir tarefas específicas e estão intimamente relacionados entre si.

2.2 uso

Com os conceitos básicos acima, resumimos as etapas para usar o padrão de estratégia da seguinte forma:

  • Step1: Crie uma classe de estratégia abstrata e defina uma interface pública para a estratégia específica;

  • Step2: Crie uma classe de estratégia específica, que implemente a classe de estratégia abstrata por meio da interface e encapsula o algoritmo específico ao mesmo tempo;

  • Etapa 3: criar uma classe de ambiente, manter uma referência a uma classe de estratégia abstrata e fornecê-la ao cliente para chamada.

3. Exemplo de uso

Além do carnaval de compras Double 11, muitas outras atividades promocionais são criadas todos os anos. Imagine só, se cada atividade promocional usasse um modelo promocional, seria muito chato e hostil para usuários, comerciantes e plataformas. Portanto, para melhorar a experiência de compra do usuário e destacar as características mercadológicas dos lojistas, é necessário utilizar diferentes estratégias para diferentes ações promocionais. Aqui tomamos a estratégia de promoção como exemplo para analisar brevemente como o modo de estratégia é usado.

3.1 Implementação do código

//step1:定义抽象策略角色(Strategy):所有促销活动的共同接口
public interface Strategy {  
    void show();
}
​
//step2:定义具体策略角色(Concrete Strategy):不同类型的具体促销策略
//618大促活动 A
public class ConcreteStrategyA implements Strategy {
    @Override
    public void show() {
        System.out.println("618大促");
    }
}
​
//99大促活动 B
public class ConcreteStrategyB implements Strategy {
    @Override
    public void show() {
        System.out.println("99大促");
    }
}
​
//双11大促活动 C
public class ConcreteStrategyC implements Strategy {
    @Override
    public void show() {
        System.out.println("双11大促");
    }
}
​
//step3:定义环境角色(Context):把促销活动推送给用户,这里可理解为淘宝平台
public class Context{
    //持有抽象策略的引用
    private Strategy myStrategy;
    //生成构造方法,让平台根据传入的参数(type)选择促销活动
    public Context(Strategy strategyType) {
        this.myStrategy = strategyType;
    }
    //向用户展示促销活动
    public void taoPlatformShow(String time) {
        System.out.println(time + "的促销策略是:");
        myStrategy.show();
    }
}
​
//step4:客户端调用,需事先明确所有每种策略类如何使用
public class StrategyPattern{
  public static void main(String[] args){
        Context_TaoPlatform context;
    
        String time1 = "9月";
        Strategy strategyB = new ConcreteStrategyB();
        context = new Context(strategyB);
        context.taoPlatformShow(time1);
    
        String time2 = "11月";
        Strategy strategyC = new ConcreteStrategyC();
        context = new Context(strategyC);
        context.taoPlatformShow(time2);
    
        String time3 = "6月";
        Strategy strategyA = new ConcreteStrategyA();
        context = new Context(strategyA);
        context.taoPlatformShow(time3);
  }   
}

3.2 Saída de resultado

9月的促销策略是:
99大促
11月的促销策略是:
双11大促
6月的促销策略是:
618大促

3.3 Diagramas UML

3.4 Diferenças do Simple Factory Pattern

Como pode ser visto no exemplo de código acima e no diagrama de classe, o padrão strategy é muito semelhante ao padrão fábrica simples apresentado no artigo anterior.A principal diferença entre os dois é a classe Context e a classe fábrica. Para facilitar a comparação, vamos dar uma olhada nos códigos dessas duas classes separadamente:

public class Context_TaoPlatform{
    //持有抽象策略的引用
    private Strategy myStrategy;
    //生成构造方法,让平台根据传入的参数(type)选择促销活动
    public TaoPlatform(Strategy strategyType) {
        this.myStrategy = strategyType;
    }
    //向用户展示促销活动
    public void taoPlatformShow(String time) {
        System.out.println(time + "的促销策略是:");
        myStrategy.show();
    }
}

public class Factory{
    public static Shirt exhibit(String ShirtName){
        switch(ShirtName){
            case "女款衬衫":
                return new WomenShirt();
            case "男款衬衫":
                return new MenShirt();
            default:
                return null;
        }
    }
}

Primeiro, observe os parâmetros de recebimento: o método exibi() na classe fábrica Factory recebe uma string e retorna um objeto Shirt; a classe de ambiente Context_TaoPlatform precisa receber um objeto Strategy quando é inicializada. Ou seja: a classe fábrica cria um objeto correspondente de acordo com as condições de recebimento, e a classe Context recebe um objeto, e métodos podem ser chamados para executar os métodos desse objeto.

Por exemplo: Existem muitos tipos de canetas, suponha que haja uma fábrica responsável pela produção de canetas para diferentes finalidades.

Modo Fábrica: Produzir canetas para diversas finalidades de acordo com a finalidade dada pelo usuário, tais como: se quiser escrever caracteres de pincel, pode produzir pincéis, e se quiser escrever caracteres de caneta, pode produzir canetas. Ou seja, de acordo com um determinado atributo dado pelo usuário, um objeto que pode realizar os comportamentos correspondentes é produzido e devolvido ao usuário, e o foco está em que tipo de objeto criar.

Modo de estratégia: use a caneta produzida pela fábrica para fazer o comportamento correspondente, como: escrever caracteres de pincel com um pincel e escrever caracteres de caneta com uma caneta. Ou seja, de acordo com um determinado objeto dado pelo usuário, o método correspondente é executado, e o foco está em qual comportamento escolher.

Quatro, apreciação do código-fonte JDK

Aqui, tomamos o comparador Comparator como exemplo e entendemos o modo de estratégia em profundidade analisando sua implementação de código-fonte.

No JDK, quando chamamos um método de ordenação sort() da classe de ferramenta array Arrays, podemos utilizar a regra de ordenação padrão (ordem crescente), ou customizar uma regra de ordenação, ou seja, customizar a ordenação em ordem crescente ou decrescente. O código fonte é o seguinte:

public class Arrays{
    public static <T> void sort(T[] a, Comparator<? super T> c) {
        if (c == null) {
            //若没有传入Comparator接口的实现类对象,调用默认的升序排序方法
            sort(a);
        } else {
            if (LegacyMergeSort.userRequested)
                //jdk5及之前的传统归并排序,新版本中LegacyMergeSort.userRequested默认false
                legacyMergeSort(a, c);
            else
                //改进后的归并排序
                TimSort.sort(a, 0, a.length, c, null, 0, 0);
        }
    }
}

Neste ponto, precisamos passar dois parâmetros: um é o array a ser classificado e o outro é o objeto de classe de implementação da interface Comparator. Dentre elas, a interface Comparator é uma interface funcional, que define um método abstrato int compare(T o1, To2), que é usado para definir uma regra de ordenação específica. Aqui, a interface Comparator é a interface de estratégia abstrata no padrão de estratégia, que define um algoritmo de classificação, e a estratégia específica (o algoritmo de classificação específico) será definida pelo usuário, então Arrays é uma classe de ambiente e o método sort() pode passar em uma estratégia c , deixe os Arrays executarem tarefas de classificação de acordo com esta estratégia.

public class demo {
    public static void main(String[] args) {
​
        Integer[] data = {12, 2, 3, 2, 4, 5, 1};
        // 实现降序排序
        Arrays.sort(data, new Comparator<Integer>() {
             // 排序策略 降序
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });
        System.out.println(Arrays.toString(data)); //[12, 5, 4, 3, 2, 2, 1]
    }
}

Na classe de teste acima, quando chamamos o método Arrays.sort(), o segundo parâmetro é o objeto de classe de subimplementação da interface Comparator. Pode-se ver que o Comparator atua como uma função de estratégia abstrata, enquanto a classe de subimplementação específica atua como uma função de estratégia concreta, e a classe de função de ambiente Arrays deve conter uma referência à estratégia abstrata a ser chamada. Então, o método Arrays.sort() usa o método compare() na classe de subimplementação Comparator? Vamos dar uma olhada no método TimSort.sort(), o código fonte é o seguinte:

class TimSort<T> {
    static <T> void sort(T[] a, int lo, int hi, Comparator<? super T> c,
                         T[] work, int workBase, int workLen) {
        assert c != null && a != null && lo >= 0 && lo <= hi && hi <= a.length;
​
        int nRemaining  = hi - lo;
        if (nRemaining < 2)
            return;  // Arrays of size 0 and 1 are always sorted
​
        // If array is small, do a "mini-TimSort" with no merges
        if (nRemaining < MIN_MERGE) {
            int initRunLen = countRunAndMakeAscending(a, lo, hi, c);
            binarySort(a, lo, hi, lo + initRunLen, c);
            return;
        }
        ...
    }   
        
    private static <T> int countRunAndMakeAscending(T[] a, int lo, int hi,Comparator<? super T> c) {
        assert lo < hi;
        int runHi = lo + 1;
        if (runHi == hi)
            return 1;
​
        // Find end of run, and reverse range if descending
        if (c.compare(a[runHi++], a[lo]) < 0) { // Descending
            while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0)
                runHi++;
            reverseRange(a, lo, runHi);
        } else {                              // Ascending
            while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0)
                runHi++;
        }
        return runHi - lo;
    }
}

O código acima será finalmente executado no método countRunAndMakeAscending(), e o método compare() será chamado quando a instrução de julgamento for executada. Então, se você usar apenas o método compare(), precisará passar apenas o objeto de classe do método de substituição compare() específico ao chamar o método Arrays.sort().

5. Vantagens e desvantagens e cenários aplicáveis

5.1 Vantagens

  • As classes de estratégia específicas podem ser trocadas livremente, uma vez que todas as classes de estratégia específicas implementam a mesma interface de estratégia abstrata, elas podem ser trocadas livremente.

  • Ele suporta o "princípio de abrir-fechar" Ao adicionar uma nova estratégia, você só precisa adicionar uma classe de estratégia específica e basicamente não precisa alterar o código original.

  • Evite usar várias instruções de seleção condicional (caso contrário), incorpore totalmente a ideia de design orientado a objetos.

5.2 Desvantagens

  • O cliente deve conhecer todas as classes de estratégias concretas, entender a diferença entre diferentes estratégias concretas e decidir qual classe de estratégia usar.

  • O padrão de estratégia gerará muitas classes de estratégia específicas, o que aumenta o número de classes no sistema até certo ponto (o número de objetos pode ser reduzido até certo ponto usando o padrão flyweight).

5.3 Cenários aplicáveis

  • Quando um sistema precisa escolher dinamicamente um dos vários algoritmos, cada algoritmo pode ser encapsulado em uma classe de estratégia específica.

  • Uma classe define vários comportamentos, e esses comportamentos aparecem na forma de várias declarações condicionais na operação dessa classe. Você pode mover cada ramificação condicional para suas respectivas classes de estratégia para substituir essas declarações condicionais e pode evitar o uso difícil de manter várias condicionais declarações de seleção e incorporam os conceitos envolvidos na orientação a objetos.

  • Cada algoritmo no sistema é completamente independente um do outro, e os detalhes de implementação do algoritmo específico devem ser ocultados dos clientes para melhorar a confidencialidade e a segurança do algoritmo.

  • A única diferença entre várias classes é que elas se comportam de maneira diferente.Você pode usar o modo de estratégia para selecionar dinamicamente o comportamento específico a ser executado em tempo de execução.

6. Resumo

O padrão de estratégia é um padrão de projeto relativamente fácil de entender e usar, que apenas encapsula algoritmos para facilitar a inserção de novos algoritmos no sistema e a retirada de algoritmos antigos do sistema. Este artigo mencionou na análise das deficiências do padrão de estratégia que o padrão de estratégia não determina quando usar qual algoritmo, e a seleção do algoritmo é determinada pelo cliente. Embora isso melhore a flexibilidade do sistema até certo ponto, o o cliente precisa entender A distinção entre todas as classes de estratégias concretas, para escolher o algoritmo adequado, aumenta a dificuldade de uso pelo cliente.

O padrão de estratégia e o padrão de fábrica têm algumas semelhanças em sua estrutura de padrão, por isso às vezes é confuso. Na verdade, existem muitas diferenças entre os dois: o modo fábrica é um modo criacional, sua função é criar objetos, foca em como criar objetos e resolve principalmente a distribuição unificada de recursos, completamente independente da criação de objetos , permitindo que a criação não tenha nada a ver com o cliente específico; o padrão de estratégia é um padrão de comportamento, a função é permitir que um objeto escolha um comportamento entre muitos comportamentos, presta atenção em como o comportamento é encapsulado e realiza a troca flexível e expansão da estratégia definindo a família de estratégias, e Fazer alterações na apólice independentemente do cliente que usa a apólice.

Além disso, em muitos cenários, o modo de estratégia e o modo de fábrica podem ser usados ​​em combinação para desempenhar um papel complementar. Por exemplo, uma das desvantagens do padrão de estratégia é que o usuário deve conhecer todos os algoritmos de estratégia específica, de modo que a estratégia específica será inevitavelmente exposta e inicializada pelo módulo superior, o que é contrário à lei de Dimit (o princípio do mínimo conhecimento), enquanto o módulo superior e a camada inferior O desacoplamento entre os módulos pode ser feito pelo modo de fábrica. Após a combinação dos dois, o módulo superior não precisa conhecer cada estratégia específica, desde que o modo de estratégia possa ser realizado por meio do Contexto.

Acho que você gosta

Origin blog.csdn.net/AlibabaTech1024/article/details/130984749
Recomendado
Clasificación