Lado da pele de camarão: caligrafia a Modo de estratégia (modo de estratégia)

diga na frente

Na área de troca de leitores (50+) do arquiteto Nien, de 40 anos , recentemente orientei um pequeno parceiro para entrevistar arquitetos. As empresas entrevistadas incluíam Shopee, Xiyin, Meituan e outras grandes empresas. O salário-alvo era superior a 50 mil . Eu encontrei uma pergunta relativamente rudimentar:

  • Por favor, escreva um padrão de estratégia (padrão de estratégia)
  • Ou escreva um modo de modelo manualmente (modo de modelo)
  • Ou escreva um modo proxy (modo proxy)

O amiguinho esqueceu, todo o resto estava bem preparado, mas no lugar desse conhecimento básico, tropecei .

Aqui, Nien lhe dará uma triagem sistemática e sistemática, para que você possa demonstrar totalmente seus fortes "músculos técnicos" e fazer o entrevistador amar "não consigo evitar, babando" .

Inclua também esta pergunta e a resposta de referência em nossa versão V85 " Nin Java Interview Collection ", para referência dos seguintes amigos, para melhorar a arquitetura, o design e o nível de desenvolvimento de todos.

Para obter os arquivos PDF das últimas "Notas de Arquitetura Nin", "Trilogia Nin High Concurrency" e "Coleção de Entrevistas Nin Java", acesse a conta oficial [Technical Freedom Circle] para obter.

Definição de Padrão de Estratégia

O padrão de estratégia é um dos padrões de comportamento no padrão de projeto.O padrão de estratégia resolve principalmente o problema de muitas opções, evitando um grande número de if else e muitos casos sob o switch.

O foco do padrão de estratégia não é como implementar algoritmos, mas como organizar e chamar esses algoritmos, para que a estrutura do programa seja mais flexível, com melhor manutenibilidade e escalabilidade

Modo de estratégia:

  • Este padrão define uma série de algoritmos e encapsula cada algoritmo para que possam ser substituídos uns pelos outros
  • O padrão Strategy permite que um algoritmo varie independentemente dos clientes que o utilizam.

Uma estratégia é uma expressão vívida. A chamada estratégia é um plano. Na vida cotidiana, existem muitos planos para atingir metas e cada plano é chamado de estratégia .

Por exemplo, quando saímos, escolhemos diferentes métodos de viagem, como andar de bicicleta, ônibus, trem, avião, foguete, etc. Cada um desses métodos de viagem é uma estratégia.

Outro exemplo é quando vamos ao shopping, o shopping está em atividade no momento, há descontos, descontos totais, abatimentos, etc. análise. Esses algoritmos em si são apenas uma estratégia. E esses algoritmos podem ser substituídos uns pelos outros a qualquer momento, por exemplo, para o mesmo produto, 20% de desconto hoje e 30% de desconto amanhã, essas estratégias são intercambiáveis.

Situações semelhantes são frequentemente encontradas no desenvolvimento de software. Existem várias maneiras de alcançar uma determinada função. Neste momento, um padrão de design pode ser usado para permitir que o sistema escolha uma solução com flexibilidade, e também é conveniente adicionar uma nova solução. É o padrão de estratégia.

Análise de Cenário do Padrão de Estratégia

Este modo nos permite escolher diferentes estratégias para completar a tarefa de acordo com diferentes ambientes ou condições.

Vejamos primeiro a imagem abaixo. Existem muitos meios de transporte para escolhermos. Podemos andar de bicicleta, pegar um carro, pegar um trem ou pegar um avião.

Como programador, você precisa escolher uma ferramenta de desenvolvimento para desenvolvimento. Claro, existem muitas ferramentas para desenvolvimento de código. Você pode escolher Idea para desenvolvimento, também pode usar o Eclipse para desenvolvimento e também pode usar algumas outras ferramentas de desenvolvimento.

No desenvolvimento de software, muitas vezes encontramos situações semelhantes. Existem várias maneiras de alcançar uma determinada função, e cada maneira corresponde a um algoritmo. Neste momento, podemos usar um padrão de design para escolher com flexibilidade o caminho da solução ou Novas soluções podem ser facilmente adicionado.

A maior vantagem do modo de estratégia: é conveniente para a expansão horizontal da função do código.O modo de estratégia encapsula o método de solução, o que é benéfico para adicionarmos ou excluirmos o método de solução.

Ao mesmo tempo, o padrão de estratégia ( Padrão de estratégia ) também está em conformidade com o princípio de abertura e fechamento .

O papel principal do padrão de estratégia

No modo de estratégia, podemos definir algumas classes independentes para encapsular diferentes algoritmos. Cada classe encapsula um algoritmo específico. Aqui, cada classe que encapsula um algoritmo pode ser chamada de Estratégia. Para garantir que essas estratégias sejam consistentes no uso, um classe de estratégia abstrata é geralmente fornecida para definir as regras, e cada algoritmo corresponde a uma classe de estratégia específica.

O padrão de estratégia envolve três papéis, como segue:

  • Classe Abstract Strategy (Strategy) : Esta é uma função abstrata, geralmente implementada por uma interface ou classe abstrata. Essa função fornece a interface exigida por todas as classes de estratégia concreta e todas as classes de estratégia concreta devem implementar essa interface. A classe de ambiente (contexto) Context usa essa interface para chamar classes de estratégias específicas.
  • Classe Concrete Strategy (Estratégia Concreta) : implementa a interface definida pela estratégia abstrata e fornece implementação ou comportamento de algoritmo específico.
  • Classe de ambiente (contexto) : usado para configurar um objeto de estratégia de algoritmo específico, manter uma referência de tipo de interface de estratégia (referência) e pode definir uma interface para objetos específicos da estratégia de interface para acessar. Em casos simples, a classe Context pode ser omitida.

O padrão de estratégia é um padrão de projeto relativamente fácil de entender e usar.O padrão de estratégia é o encapsulamento do algoritmo.Ele separa a responsabilidade do algoritmo do próprio algoritmo e a delega a diferentes objetos para gerenciamento.

O padrão de estratégia geralmente encapsula uma série de algoritmos em uma série de classes de estratégia concretas como subclasses da classe de estratégia abstrata.

No padrão de estratégia, é muito importante entender a classe de ambiente e a classe de estratégia abstrata.A classe de ambiente é a classe que precisa usar o algoritmo. Várias classes de ambiente podem existir em um sistema e podem precisar reutilizar alguns dos mesmos algoritmos.

No código do cliente, apenas um objeto de política específico precisa ser injetado, e o nome da classe de política específica pode ser armazenado no arquivo de configuração, e o objeto de política específico pode ser criado dinamicamente por meio de reflexão, para que o usuário possa substituir com flexibilidade o específico classe de política e adicionar novas políticas específicas.As classes de estratégia também são úteis.

O modo de estratégia fornece uma implementação de algoritmo conectável (Pluggable).

O principal objetivo do padrão de estratégia é separar a definição do algoritmo de seu uso, ou seja, separar o comportamento do algoritmo do ambiente e colocar a definição do algoritmo em uma classe de estratégia especial. encapsula um algoritmo de implementação e o uso do algoritmo A classe de ambiente é programada em relação à classe de estratégia abstrata, que está em conformidade com o "princípio de inversão de dependência" . Quando um novo algoritmo aparece, é necessário apenas adicionar uma nova classe de estratégia concreta que implemente a classe de estratégia abstrata.

Implementação Java do Padrão de Estratégia

1. Crie uma classe de estratégia abstrata

2. Crie estratégias concretas

(1) Estratégia específica A

(2) Estratégia específica B

(3) Estratégia específica N...

3. Crie uma classe de ambiente

step1: Crie uma classe de estratégia abstrata

  • Define a interface pública para todos os algoritmos suportados.
  • Todas as classes de estratégia concreta devem implementar essa interface.
  • O contexto usa essa interface para chamar um algoritmo definido por um concretoStrategy;

Definir uma interface comum para todas as promoções em uma loja de departamentos

package com.crazymakercircle.designpattern.strategy;

//抽象策略类
public interface Strategy {
    
    
    void show();
}

Passo 2: Crie uma estratégia específica

A classe de estratégia é ainda dividida e cada algoritmo promocional é encapsulado individualmente em uma classe, ou seja, uma classe é dividida em várias classes e cada classe encapsula um algoritmo de estratégia promocional separadamente.

Desta forma, modificar um algoritmo só precisa recompilar a classe envolvida no algoritmo, e nenhuma outra classe precisa ser recompilada. Se você quiser adicionar um novo algoritmo, basta adicionar uma nova classe que encapsula o algoritmo na coleção de subclasses.

Definir papéis estratégicos específicos (Estratégia Concreta): ações promocionais específicas para cada festival

//为春节准备的促销活动A
public class StrategyA implements Strategy {
    
    

    public void show() {
    
    
        System.out.println("买一送一");
    }
}

//为中秋准备的促销活动B
public class StrategyB implements Strategy {
    
    

    public void show() {
    
    
        System.out.println("满200元减50元");
    }
}

//为圣诞准备的促销活动C
public class StrategyC implements Strategy {
    
    

    public void show() {
    
    
        System.out.println("满1000元加一元换购任意200元以下商品");
    }
}

passo 3: criar classe de ambiente

Context geralmente carrega e inicializa uma ConcreteStrategy específica com base na configuração, e os métodos de configuração são diversos:

  • variável de ambiente do sistema
  • arquivo xml
  • base de dados
  • etc.

O contexto encaminha a solicitação de seu cliente para sua estratégia.

O cliente só interage com o Contexto. Os clientes geralmente obtêm ConcreteStrategy do contexto, então o Context geralmente tem uma série de classes ConcreteStrategy para os clientes escolherem.

package com.crazymakercircle.designpattern.strategy;

import lombok.Data;

@Data
public class Context {
    
    

    String type = System.getenv("strategy");
    Strategy strategy = null;

    public Context() {
    
    
        switch (type) {
    
    
            case "1":
                strategy = new StrategyA();
                break;
            case "2":
                strategy = new StrategyB();
                break;
            case "3":
                strategy = new StrategyC();
                break;
            default:
                strategy = new StrategyA();
        }
    }
}

Ao executar, defina a variável de ambiente

passo 4: criar classe de cliente

Implementação do código GO do padrão de estratégia

Em seguida, vamos começar a mostrar o modo de estratégia na forma de código GO.O código é muito simples e usamos um método de adição, subtração, multiplicação e divisão para simular.

Primeiramente, veremos a interface de estratégia e uma série de estratégias que não dependem da implementação de módulos de alto nível.

package strategy
/**
 * 策略接口
 */
type Strategier interface {
    
    
    Compute(num1, num2 int) int
}

Uma interface bem simples que define um método Computeque aceita dois parâmetros e retorna um valor do tipo int. É fácil entender que a estratégia que queremos implementar retornará o valor calculado dos dois parâmetros.
Em seguida, vamos ver uma estratégia que implementamos,

package strategy
import "fmt"

type Division struct {
    
    }

func (p Division) Compute(num1, num2 int) int {
    
    
    defer func() {
    
    
        if f := recover(); f != nil {
    
    
            fmt.Println(f)
            return
        }
    }()

    if num2 == 0 {
    
    
        panic("num2 must not be 0!")
    }

    return num1 / num2
}

Por que tomar a divisão como representante? Como a divisão é especial, o dividendo não pode ser 0 e outras adições, subtrações e multiplicações são basicamente tratadas por uma linha de código. Para a divisão, precisamos determinar se o dividendo é 0 e, se for 0, uma exceção será ser lançado diretamente.
Ok, a estratégia básica está definida, também precisamos de um método de fábrica para retornar diferentes estratégias de acordo com diferentes tipos, esse tipo vamos inserir no comando.

func NewStrategy(t string) (res Strategier) {
    
    
    switch t {
    
    
        case "s": // 减法
            res = Subtraction{
    
    }
        case "m": // 乘法
            res = Multiplication{
    
    }
        case "d": // 除法
            res = Division{
    
    }
        case "a": // 加法
            fallthrough
        default:
            res = Addition{
    
    }
    }

    return
}

Este método de fábrica retornará diferentes implementações de estratégia de acordo com diferentes tipos. Claro, se precisarmos adicionar uma nova estratégia algum dia, precisamos apenas adicionar o julgamento de tipo correspondente nesta função e estará tudo bem.

Agora que a estratégia parece ter sido concluída, vamos dar uma olhada no código do processo principal, um Computador,

package compute

import (
    "fmt"
    s "../strategy"
)

type Computer struct {
    
    
    Num1, Num2 int
    strate s.Strategier
}

func (p *Computer) SetStrategy(strate s.Strategier) {
    
    
    p.strate = strate
}

func (p Computer) Do() int {
    
    
    defer func() {
    
    
        if f := recover(); f != nil {
    
    
            fmt.Println(f)
        }
    }()

    if p.strate == nil {
    
    
        panic("Strategier is null")
    }

    return p.strate.Compute(p.Num1, p.Num2)
}

Existem três parâmetros neste Computador, Num1e Num2é claro que é o número que queremos operar. A estratégia é a estratégia que queremos definir. Pode ser a apresentada acima, Divisionou pode ser outra. Na função principal, nós irá chamar SetStrategyo método para definir a estratégia a ser utilizada. Strategy, Doo método irá realizar o cálculo, e por fim retornar o resultado do cálculo. Você pode ver que Dodelegamos a função de cálculo para Strategier.

Parece que está tudo pronto, vamos escrever o código do main.

package main

import (
    "fmt"
    "flag"
    c "./computer"
    s "./strategy"
)

var stra *string = flag.String("type", "a", "input the strategy")
var num1 *int = flag.Int("num1", 1, "input num1")
var num2 *int = flag.Int("num2", 1, "input num2")

func init() {
    
    
    flag.Parse()
}

func main() {
    
    
    com := c.Computer{
    
    Num1: *num1, Num2: *num2}
    strate := s.NewStrategy(*stra)

    com.SetStrategy(strate)
    fmt.Println(com.Do())
}

Primeiro, precisamos ler o tipo de estratégia e dois operandos a serem usados ​​na linha de comando. Na função principal, inicializamos a estrutura e atribuímos Computeros operandos de entrada à soma. Em seguida, chamamos a função de acordo com o tipo de estratégia o método para definir a estratégia obtida acima e, finalmente, execute o resultado do cálculo do método e, finalmente, imprima-o.ComputerNum1Num2NewStrategyComputerSetStrategyComputerDo

É simples assim, agora navegamos até main.goo diretório onde estamos na linha de comando, e executamos o comando para compilar o arquivo

ir construir main.go

continuar com o comando

principal -tipo d -num1 4 -num2 2

Vamos tentar usar a estratégia de adição para operar os dois números 4 e 2, e ver qual é o resultado,

O resultado está correto, tente outra estratégia, faça uma multiplicação e execute o comando

principal -tipo m -num1 4 -num2 2

O resultado também está correto.

Vantagens e desvantagens do padrão de estratégia

vantagem

  1. O foco do padrão de estratégia não é como implementar algoritmos, mas como organizar e chamar esses algoritmos, para que a estrutura do programa seja mais flexível, com melhor manutenibilidade e escalabilidade.
  2. Cada algoritmo de estratégia no padrão de estratégia é igual. Para uma série de algoritmos de política específicos, o status é exatamente o mesmo. É precisamente por causa dessa igualdade que os algoritmos podem ser substituídos entre si. Todos os algoritmos de política também são independentes entre si em termos de implementação e não há dependência um do outro. Portanto, esta série de algoritmos de política pode ser descrita assim: Algoritmos de política são diferentes implementações do mesmo comportamento.
  3. Durante a operação, o modo de estratégia só pode usar um objeto de implementação de estratégia específico em cada momento. Embora possa alternar dinamicamente entre diferentes implementações de estratégia, apenas um pode ser usado ao mesmo tempo.

Se todas as classes de estratégia concreta tiverem algum comportamento público. Neste momento, esses comportamentos públicos devem ser colocados na classe Strategy de função de estratégia abstrata comum. É claro que, neste momento, a função de estratégia abstrata deve ser implementada com uma classe abstrata Java em vez de uma interface. No entanto, não há bala de prata na programação e o modo de estratégia não é exceção. Também existem algumas desvantagens. Primeiro, vamos revisar e resumir as vantagens:

  1. O modo de estratégia fornece suporte perfeito para o "princípio de abertura-fechamento" Os usuários podem escolher algoritmos ou comportamentos sem modificar o sistema original e também podem adicionar novos algoritmos ou comportamentos com flexibilidade.
  2. O padrão Strategy fornece uma maneira de gerenciar famílias de algoritmos relacionados. Uma hierarquia de classes de estratégia define um algoritmo ou família de comportamentos. O uso adequado da herança pode mover o código comum para a classe pai, evitando assim a duplicação de código.
  3. Use o padrão de estratégia para evitar o uso de várias instruções condicionais (if-else). Múltiplas declarações condicionais não são fáceis de manter. Ele mistura a lógica de qual algoritmo ou comportamento adotar com a lógica do algoritmo ou comportamento e lista todos eles em uma declaração condicional múltipla, que é mais primitiva e retrógrada do que o método de herança .

deficiência

  1. Os clientes devem estar cientes de todas as classes de políticas e decidir por si mesmos qual delas usar. Isso significa que o cliente deve entender a diferença entre esses algoritmos para selecionar a classe de algoritmo apropriada no momento certo. A criação e seleção dessa classe de estratégia podem ser auxiliadas pelo modelo de fábrica.
  2. Como o padrão de estratégia encapsula cada implementação de estratégia específica separadamente em uma classe, se houver muitas estratégias alternativas, o número de objetos será considerável. O número de objetos pode ser reduzido até certo ponto usando o padrão flyweight .

Discussão sobre o padrão de estratégia

O padrão de estratégia é relativamente fácil de entender.O núcleo do padrão de estratégia é separar o código facilmente alterável da lógica principal, padronizar sua forma por meio de uma interface e delegar tarefas à estratégia na lógica principal. Fazer isso não apenas reduz a possibilidade de modificarmos o código lógico principal, mas também aumenta a escalabilidade do sistema.

Princípios básicos de design: aberto para extensão, fechado para modificação

Existem dois pontos de partida principais para usar o padrão de estratégia:

(1) Encapsule um grupo de algoritmos relacionados em cada ramificação da estratégia, ocultando assim o código relacionado à ramificação da estratégia.

(2) Espera-se que a escalabilidade do programa possa ser melhorada.

A seguir discutimos brevemente a escalabilidade do padrão strategy.De fato, a intenção original do padrão strategy é reduzir as declarações condicionais relacionadas ao comportamento de cada ramificação. Isso foi resolvido dividindo uma classe com vários comportamentos relacionados a condições em uma superclasse de estratégia e várias subclasses de estratégia. Ou seja, altere a classe única original que contém várias instruções condicionais para uma classe de hierarquia de política sem instruções condicionais.

Embora pareça que a instrução condicional desapareceu aqui, não há nenhuma instrução condicional relacionada à subclasse de política no programa cliente e à classe Context? A resposta é claro que não.

De fato, no projeto do padrão de estratégia, a classe cliente é responsável por criar objetos de diferentes subclasses de estratégia de acordo com diferentes condições e, em seguida, passar o objeto para a classe de ambiente Context. O papel da classe Context pode ser entendido como: sendo a chamada subclasse strategy Alguns métodos do Client fornecem alguns parâmetros, e utilizam o objeto passado pela classe Client para chamar alguns métodos da classe Strategy.

Isso mostra que existem muitas instruções condicionais relacionadas à subclasse de ramificação de política na classe cliente Client, mas não há tal instrução na classe Context.

Então, a responsabilidade de criar o objeto da subclasse de política pode ser dada à classe Context, e a classe cliente Client fornece apenas alguns parâmetros que representam a solicitação do cliente para a classe Context?

(1) A classe cliente é responsável por criar o objeto da subclasse strategy

A classe cliente é responsável por criar objetos de diferentes subclasses de política de acordo com diferentes solicitações fornecidas pelos usuários e, em seguida, transfere os objetos para a classe Context . Nesse caso, a classe do cliente geralmente contém instruções condicionais relacionadas à política, mas não há necessidade de usar nenhuma instrução condicional relacionada à política na classe Context. Portanto, modificar ou adicionar uma subclasse de política não precisa modificar a classe Context. No entanto, no caso de adicionar uma nova subclasse de estratégia, se a classe cliente precisar usar a subclasse , muitas vezes é necessário adicionar uma nova instrução condicional na classe cliente, ou seja, a classe cliente precisa ser modificada.

(2) A classe Context é responsável por criar o objeto da subclasse política

A responsabilidade de criar o objeto da subclasse de estratégia é dada à classe Context, e a classe cliente Client fornece apenas alguns parâmetros que representam o pedido do cliente para a classe Context; neste caso, quando a classe Context cria o objeto da subclasse de estratégia , ele deve usar as mesmas instruções condicionais relacionadas às subclasses de política. Neste ponto, modificar uma subclasse de política não requer modificar as classes Client e Context . Ao adicionar uma nova subclasse de estratégia, se a classe cliente não usar a nova subclasse temporariamente, a adição da nova subclasse não afetará os códigos-fonte da classe cliente e da classe Context. No entanto, se a classe de cliente quiser usar a nova subclasse de política, uma nova ramificação condicional deve ser adicionada à classe de cliente e à classe de contexto ao mesmo tempo , ou seja, a classe de cliente e a classe de contexto precisam ser modificadas no mesmo tempo .

Nos dois casos acima, quando apenas o código da subclasse de estratégia precisa ser modificado, nem a classe cliente nem a classe Context precisam ser modificadas.

Em resumo, o design de objetos criados por classes cliente é mais escalável. Dessa forma, as instruções condicionais relacionadas às subclasses de política podem aparecer na classe Context, melhorando assim a escalabilidade.

Cenários de Aplicação do Padrão Strategy

  • Cenários em que várias classes diferem apenas ligeiramente em algoritmo ou comportamento
  • Algoritmos precisam mudar livremente
  • Cenários em que as regras algorítmicas precisam ser protegidas
  • Modo de viagem, bicicleta, carro, etc., cada modo de viagem é uma estratégia
  • Métodos de promoção de shopping centers, descontos, descontos totais, etc.

A diferença entre padrão de estratégia e padrão de fábrica

padrão de fábrica

  1. O objetivo é criar objetos distintos e relacionados
  2. Concentre-se em "criar objetos"
  3. O método de implementação pode ser por meio da classe pai ou interface
  4. Geralmente, a criação de um objeto deve ser um mapeamento de algo no mundo real, com suas próprias propriedades e métodos

padrão de estratégia

  1. O objetivo é substituir facilmente diferentes classes de algoritmos
  2. Foco na implementação do algoritmo (comportamento)
  3. Implementado principalmente através da interface
  4. Crie um objeto que abstraia o comportamento em vez do objeto e provavelmente não tenha propriedades próprias

diga no final

Escrever à mão alguns padrões básicos de design é uma pergunta muito comum em entrevistas.

Se você puder responder às duas principais soluções acima com fluência e familiaridade, basicamente o entrevistador ficará chocado e atraído por você.

No final, o entrevistador se apaixonou por "não consegue se conter, babando" , e na mesma hora veio a oferta.

Durante o processo de aprendizado, se você tiver alguma dúvida, pode procurar o arquiteto Nien, de 40 anos, para se comunicar.

Leitura relacionada recomendada

" Começando de 0, Handwriting Redis "

" Começando de 0, Handwriting MySQL Transaction ManagerTM "

" Começando de 0, Handwriting MySQL Data Manager DM "

" Jingdong é muito feroz, e o hashmap manuscrito reaparece nos rios e lagos novamente "

" Lado NetEase: como projetar o pool de threads? Por favor, escreva um pool de threads simples? "

"Nin's Architecture Notes", "Nin's High Concurrency Trilogy", "Nin's Java Interview Collection" PDF, vá para a seguinte conta oficial [Technical Freedom Circle] para obtê-lo↓↓↓

Acho que você gosta

Origin blog.csdn.net/crazymakercircle/article/details/131730324
Recomendado
Clasificación