[Padrão de Design e Paradigma: Comportamental] 62 | Padrão Cadeia de Responsabilidade (Parte 1): Como implementar uma estrutura de filtragem de informações confidenciais que pode expandir algoritmos com flexibilidade?

Nas lições anteriores, aprendemos o modo modelo e o modo estratégia. Hoje, aprenderemos o modo cadeia de responsabilidade. Esses três modos têm a mesma função: reutilização e extensão, que são mais comumente usados ​​no desenvolvimento de projetos reais, especialmente no desenvolvimento de frameworks, podemos usá-los para fornecer pontos de extensão do framework, para que os usuários do framework possam usá-lo sem modificá-lo o framework No caso do código-fonte, as funções do framework são customizadas com base em pontos de extensão.

Hoje, explicamos principalmente o princípio e a implementação do modelo de cadeia de responsabilidade. Além disso, usarei o modelo de cadeia de responsabilidade para levá-lo a implementar uma estrutura de filtragem de palavras sensível que pode expandir o algoritmo com flexibilidade. Na próxima aula vamos nos aproximar mais do combate real, analisando Servlet Filter e Spring Interceptor, veremos como usar o padrão chain of Responsibility para implementar filtros e interceptores comumente usados ​​no framework.

Sem mais delongas, vamos começar oficialmente o estudo de hoje!

Princípio e Implementação do Padrão da Cadeia de Responsabilidade

A tradução em inglês do modelo de cadeia de responsabilidade é Chain Of Responsibility Design Pattern. Nos "Design Patterns" do GoF, é definido da seguinte forma:

Evite acoplar o remetente de uma solicitação ao seu destinatário, dando a mais de um objeto a chance de lidar com a solicitação. Encadeie os objetos receptores e passe a solicitação ao longo da cadeia até que um objeto a trate.

Traduzido para o chinês é: desacoplar o envio e o recebimento da solicitação, para que vários objetos receptores tenham a oportunidade de processar a solicitação. Encadeie esses objetos de recebimento em uma cadeia e passe a solicitação ao longo da cadeia até que um dos objetos de recebimento na cadeia possa tratá-la.

Isso é bastante abstrato, então vou explicar melhor em palavras mais fáceis de entender.

No padrão Chain of Responsibility, vários processadores (ou seja, o "objeto receptor" mencionado na definição) processam a mesma solicitação em sequência. Uma requisição é primeiramente processada pelo processador A, e depois passada para o processador B, após o processamento pelo processador B, é passada para o processador C, e assim sucessivamente, formando uma cadeia. Cada processador na cadeia assume suas próprias responsabilidades de processamento, por isso é chamado de modo de cadeia de responsabilidade.

Em relação ao modelo de cadeia de responsabilidade, vamos dar uma olhada em sua implementação de código. Combinado com a implementação do código, será mais fácil para você entender sua definição. Existem muitas maneiras de implementar o modo de cadeia de responsabilidade e apresentamos aqui duas comumente usadas.

A primeira implementação é mostrada abaixo. Entre eles, Handler é a classe pai abstrata de todas as classes de processadores, e handle() é um método abstrato. A estrutura de código da função handle() de cada classe de handler específica (HandlerA, HandlerB) é similar. processador subseqüente (ou seja, chamando success.handle()). HandlerChain é uma cadeia de processamento.Do ponto de vista da estrutura de dados, é uma lista encadeada que registra o início e o final da cadeia. Entre eles, a cauda da cadeia de registro é para a conveniência de adicionar processadores.

public abstract class Handler {
  protected Handler successor = null;
  public void setSuccessor(Handler successor) {
    this.successor = successor;
  }
  public abstract void handle();
}
public class HandlerA extends Handler {
  @Override
  public boolean handle() {
    boolean handled = false;
    //...
    if (!handled && successor != null) {
      successor.handle();
    }
  }
}
public class HandlerB extends Handler {
  @Override
  public void handle() {
    boolean handled = false;
    //...
    if (!handled && successor != null) {
      successor.handle();
    } 
  }
}
public class HandlerChain {
  private Handler head = null;
  private Handler tail = null;
  public void addHandler(Handler handler) {
    handler.setSuccessor(null);
    if (head == null) {
      head = handler;
      tail = handler;
      return;
    }
    tail.setSuccessor(handler);
    tail = handler;
  }
  public void handle() {
    if (head != null) {
      head.handle();
    }
  }
}
// 使用举例
public class Application {
  public static void main(String[] args) {
    HandlerChain chain = new HandlerChain();
    chain.addHandler(new HandlerA());
    chain.addHandler(new HandlerB());
    chain.handle();
  }
}

Na verdade, a implementação do código acima não é suficientemente elegante. A função handle() da classe do processador não apenas contém sua própria lógica de negócios, mas também contém a chamada para o próximo processador, ou seja, success.handle() no código. Um programador que não esteja familiarizado com essa estrutura de código pode esquecer de chamar success.handle() na função handle() ao adicionar uma nova classe de manipulador, o que levará a erros no código.

Em resposta a esse problema, refatoramos o código e usamos o padrão de modelo para separar a lógica de chamar success.handle() da classe de manipulador específica e colocá-la na classe pai abstrata. Dessa forma, a classe de processador específica precisa apenas implementar sua própria lógica de negócios. O código após a refatoração fica assim:

public abstract class Handler {
  protected Handler successor = null;
  public void setSuccessor(Handler successor) {
    this.successor = successor;
  }
  public final void handle() {
    boolean handled = doHandle();
    if (successor != null && !handled) {
      successor.handle();
    }
  }
  protected abstract boolean doHandle();
}
public class HandlerA extends Handler {
  @Override
  protected boolean doHandle() {
    boolean handled = false;
    //...
    return handled;
  }
}
public class HandlerB extends Handler {
  @Override
  protected boolean doHandle() {
    boolean handled = false;
    //...
    return handled;
  }
}
// HandlerChain和Application代码不变

Vejamos a segunda implementação, o código é o seguinte. Esta implementação é mais simples. A classe HandlerChain usa uma matriz em vez de uma lista vinculada para salvar todos os processadores e precisa chamar a função handle() de cada processador por vez na função handle() de HandlerChain.

public interface IHandler {
  boolean handle();
}
public class HandlerA implements IHandler {
  @Override
  public boolean handle() {
    boolean handled = false;
    //...
    return handled;
  }
}
public class HandlerB implements IHandler {
  @Override
  public boolean handle() {
    boolean handled = false;
    //...
    return handled;
  }
}
public class HandlerChain {
  private List<IHandler> handlers = new ArrayList<>();
  public void addHandler(IHandler handler) {
    this.handlers.add(handler);
  }
  public void handle() {
    for (IHandler handler : handlers) {
      boolean handled = handler.handle();
      if (handled) {
        break;
      }
    }
  }
}
// 使用举例
public class Application {
  public static void main(String[] args) {
    HandlerChain chain = new HandlerChain();
    chain.addHandler(new HandlerA());
    chain.addHandler(new HandlerB());
    chain.handle();
  }
}

Na definição dada pelo GoF, se um processador na cadeia de processadores puder lidar com a solicitação, ele não continuará repassando a solicitação. Na verdade, existe outra variante do modo de cadeia de responsabilidade, ou seja, a solicitação será processada por todos os processadores e não haverá rescisão intermediária. Esta variante também possui duas implementações: usar uma lista vinculada para armazenar o processador e usar uma matriz para armazenar o processador, que são semelhantes às duas implementações anteriores e precisam apenas ser ligeiramente modificadas.

Apresento apenas um dos métodos de implementação aqui, conforme mostrado abaixo. De outra forma, você mesmo pode modificá-lo de acordo com a implementação acima.

public abstract class Handler {
  protected Handler successor = null;
  public void setSuccessor(Handler successor) {
    this.successor = successor;
  }
  public final void handle() {
    doHandle();
    if (successor != null) {
      successor.handle();
    }
  }
  protected abstract void doHandle();
}
public class HandlerA extends Handler {
  @Override
  protected void doHandle() {
    //...
  }
}
public class HandlerB extends Handler {
  @Override
  protected void doHandle() {
    //...
  }
}
public class HandlerChain {
  private Handler head = null;
  private Handler tail = null;
  public void addHandler(Handler handler) {
    handler.setSuccessor(null);
    if (head == null) {
      head = handler;
      tail = handler;
      return;
    }
    tail.setSuccessor(handler);
    tail = handler;
  }
  public void handle() {
    if (head != null) {
      head.handle();
    }
  }
}
// 使用举例
public class Application {
  public static void main(String[] args) {
    HandlerChain chain = new HandlerChain();
    chain.addHandler(new HandlerA());
    chain.addHandler(new HandlerB());
    chain.handle();
  }
}

Exemplos de Cenários de Aplicação do Padrão Chain of Responsibility

Agora que o princípio e a implementação do padrão Chain of Responsibility estão finalizados, vamos conhecer o cenário de aplicação do padrão Chain of Responsibility através de um exemplo prático.

Para aplicativos (como fóruns) que oferecem suporte a UGC (conteúdo gerado pelo usuário, conteúdo gerado pelo usuário), o conteúdo gerado pelo usuário (como postagens publicadas em fóruns) pode conter algumas palavras sensíveis (como pornografia, publicidade, vocabulário reacionário, etc. ). Para esse cenário de aplicativo, podemos usar o padrão de cadeia de responsabilidade para filtrar essas palavras confidenciais.

Para conteúdo que contém palavras sensíveis, temos duas maneiras de lidar com isso. Uma é proibir a publicação diretamente e a outra é agrupar palavras sensíveis (por exemplo, substituir palavras sensíveis por ***) antes da publicação. O primeiro método de processamento está em conformidade com a definição do modelo de cadeia de responsabilidade dada pelo GoF, e o segundo método de processamento é uma variante do modelo de cadeia de responsabilidade.

Fornecemos apenas o exemplo de código da primeira implementação aqui, conforme mostrado abaixo, e fornecemos apenas o esqueleto da implementação do código, e o algoritmo específico de filtragem de palavras sensíveis não é fornecido, você pode consultar minha outra coluna " Os capítulos relevantes de A estrutura de dados e a correspondência de cadeias de vários padrões de algoritmo são auto-implementadas.

public interface SensitiveWordFilter {
  boolean doFilter(Content content);
}
public class SexyWordFilter implements SensitiveWordFilter {
  @Override
  public boolean doFilter(Content content) {
    boolean legal = true;
    //...
    return legal;
  }
}
// PoliticalWordFilter、AdsWordFilter类代码结构与SexyWordFilter类似
public class SensitiveWordFilterChain {
  private List<SensitiveWordFilter> filters = new ArrayList<>();
  public void addFilter(SensitiveWordFilter filter) {
    this.filters.add(filter);
  }
  // return true if content doesn't contain sensitive words.
  public boolean filter(Content content) {
    for (SensitiveWordFilter filter : filters) {
      if (!filter.doFilter(content)) {
        return false;
      }
    }
    return true;
  }
}
public class ApplicationDemo {
  public static void main(String[] args) {
    SensitiveWordFilterChain filterChain = new SensitiveWordFilterChain();
    filterChain.addFilter(new AdsWordFilter());
    filterChain.addFilter(new SexyWordFilter());
    filterChain.addFilter(new PoliticalWordFilter());
    boolean legal = filterChain.filter(new Content());
    if (!legal) {
      // 不发表
    } else {
      // 发表
    }
  }
}

Depois de ler a implementação acima, você pode dizer, também posso implementar a função de filtragem de palavras sensíveis como a seguir, e o código é mais simples, por que tenho que usar o modo de cadeia de responsabilidade? Isso é superprojetado?

public class SensitiveWordFilter {
  // return true if content doesn't contain sensitive words.
  public boolean filter(Content content) {
    if (!filterSexyWord(content)) {
      return false;
    }
    if (!filterAdsWord(content)) {
      return false;
    }
    if (!filterPoliticalWord(content)) {
      return false;
    }
    return true;
  }
  private boolean filterSexyWord(Content content) {
    //....
  }
  private boolean filterAdsWord(Content content) {
    //...
  }
  private boolean filterPoliticalWord(Content content) {
    //...
  }
}

Dissemos muitas vezes antes que o padrão de design do aplicativo é principalmente para lidar com a complexidade do código, fazê-lo atender ao princípio de abertura e fechamento e melhorar a escalabilidade do código. A aplicação do padrão Cadeia de Responsabilidade aqui não é exceção. Na verdade, quando explicamos o padrão de estratégia, também falamos sobre questões semelhantes, por exemplo, por que usar o padrão de estratégia? As razões dadas naquela época são quase as mesmas para aplicar o modelo de cadeia de responsabilidade agora. Você pode dar uma olhada em combinação com as explicações da época.

Primeiro, vamos ver como o padrão Chain of Responsibility lida com a complexidade do código.

Dividir grandes blocos de lógica de código em funções e classes grandes em classes pequenas é uma maneira comum de lidar com a complexidade do código. Aplicando o modelo de cadeia de responsabilidade, continuamos a dividir cada função de filtro de palavra sensível e projetá-la em uma classe independente, simplificando ainda mais a classe SensitiveWordFilter, para que o código da classe SensitiveWordFilter não seja muito ou muito complicado.

Em segundo lugar, vamos ver como o modelo de cadeia de responsabilidade pode fazer o código atender ao princípio de abertura e fechamento e melhorar a escalabilidade do código.

Quando queremos expandir o novo algoritmo de filtragem, por exemplo, também precisamos filtrar símbolos especiais. De acordo com a implementação do código do modo de cadeia de não responsabilidade, precisamos modificar o código de SensitiveWordFilter, que viola o princípio de abertura e fechando. No entanto, tais modificações são relativamente concentradas e aceitáveis. A implementação do modo de cadeia de responsabilidade é mais elegante, só precisa adicionar uma nova classe Filter e adicioná-la ao FilterChain através da função addFilter(), outros códigos não precisam ser modificados.
No entanto, pode-se dizer que mesmo que o padrão Chain of Responsibility seja usado para implementá-lo, ao adicionar um novo algoritmo de filtragem, o código do cliente (ApplicationDemo) ainda precisa ser modificado, o que não atende totalmente ao princípio de abertura e fechamento .

Na verdade, se o refinarmos, podemos dividir o código acima em duas categorias: código de estrutura e código de cliente. Dentre eles, ApplicationDemo pertence ao código cliente, ou seja, o código que utiliza o framework. Códigos diferentes de ApplicationDemo pertencem ao código da estrutura de filtragem de palavras sensíveis.

Supondo que a estrutura de filtragem de palavras sensíveis não seja desenvolvida e mantida por nós, mas uma estrutura de terceiros introduzida por nós, queremos expandir um novo algoritmo de filtragem e é impossível modificar diretamente o código-fonte da estrutura. Neste momento, o modelo de cadeia de responsabilidade pode ser usado para alcançar o que foi mencionado no início, sem modificar o código-fonte do framework, novas funções podem ser estendidas com base nos pontos de extensão fornecidos pelo modelo de cadeia de responsabilidade. Em outras palavras, implementamos o princípio aberto-fechado dentro do escopo de código da estrutura.

Além disso, usar o modo de cadeia de responsabilidade tem outra vantagem em comparação com o método de implementação sem cadeia de responsabilidade, ou seja, a configuração do algoritmo de filtragem é mais flexível e você pode optar por usar apenas determinados algoritmos de filtragem.

revisão chave

Bem, isso é tudo para o conteúdo de hoje. Vamos resumir e revisar juntos, no que você precisa focar.

No padrão Chain of Responsibility, vários processadores processam a mesma solicitação sequencialmente. Uma requisição é primeiramente processada pelo processador A, e depois passada para o processador B, após o processamento pelo processador B, é passada para o processador C, e assim sucessivamente, formando uma cadeia. Cada processador na cadeia assume suas próprias responsabilidades de processamento, por isso é chamado de modo de cadeia de responsabilidade.

Na definição de GoF, uma vez que um processador pode lidar com a solicitação, ele não continuará a repassar a solicitação aos processadores subsequentes. Obviamente, no desenvolvimento real, também existem variantes desse modo, ou seja, a solicitação não será encerrada no meio do caminho, mas será processada por todos os processadores.

Existem duas implementações comuns do padrão Chain of Responsibility. Uma é usar uma lista encadeada para armazenar processadores e a outra é usar uma matriz para armazenar processadores.A última implementação é mais simples.

discussão em classe

Hoje falamos sobre o uso do modelo de cadeia de responsabilidade, podemos fazer o código da estrutura atender ao princípio de abertura e fechamento. Adicionar um novo manipulador requer apenas a modificação do código do cliente. Se quisermos que o código do cliente também satisfaça o princípio de abrir-fechar sem modificar nenhum código, como você pode fazer isso?

Acho que você gosta

Origin blog.csdn.net/qq_32907491/article/details/131269103
Recomendado
Clasificación