Tempo geek - a beleza dos padrões de design Modo construtor: explicação detalhada de três métodos de criação de objetos: construtor, método de definição e modo construtor

Modo de construtor , chinês traduzido como modo de construtor ou modo de construtor, algumas pessoas o chamam de modo de construtor.

Na verdade, o princípio e a implementação do código do padrão do construtor são muito simples e não são difíceis de dominar.A dificuldade está nos cenários do aplicativo. Por exemplo, você considerou as seguintes questões: Você pode criar objetos usando diretamente o construtor ou com o método set.Por que você precisa do modo de construtor para criá-lo? Tanto o modo construtor quanto o modo fábrica podem criar objetos, então qual é a diferença entre os dois?

Por que o modo construtor é necessário?

No desenvolvimento normal, a maneira mais comum de criar um objeto é usar a nova palavra-chave para chamar o construtor da classe. Minha pergunta é: sob quais circunstâncias esse método não é aplicável e o padrão do construtor é necessário para criar objetos? Você pode pensar sobre isso primeiro e deixe-me mostrar um exemplo.

Suponha que haja tal pergunta de entrevista de design: precisamos definir uma classe de configuração de pool de recursos ResourcePoolConfig. O pool de recursos aqui pode ser simplesmente entendido como pool de threads, pool de conexões, pool de objetos, etc. Nesta classe de configuração do pool de recursos, existem as seguintes variáveis ​​de membro, que são itens configuráveis. Agora, escreva o código para implementar esta classe ResourcePoolConfig.

Insira a descrição da imagem aqui
Contanto que você tenha um pouco de experiência em desenvolvimento, não é difícil para você implementar tal classe. As ideias de implementação mais comuns e fáceis de imaginar são mostradas no código a seguir. Como maxTotal, maxIdle e minIdle não são variáveis ​​obrigatórias, ao criar o objeto ResourcePoolConfig, passamos valores nulos para esses parâmetros no construtor para indicar o uso de valores padrão.


public class ResourcePoolConfig {
    
    
  private static final int DEFAULT_MAX_TOTAL = 8;
  private static final int DEFAULT_MAX_IDLE = 8;
  private static final int DEFAULT_MIN_IDLE = 0;

  private String name;
  private int maxTotal = DEFAULT_MAX_TOTAL;
  private int maxIdle = DEFAULT_MAX_IDLE;
  private int minIdle = DEFAULT_MIN_IDLE;

  public ResourcePoolConfig(String name, Integer maxTotal, Integer maxIdle, Integer minIdle) {
    
    
    if (StringUtils.isBlank(name)) {
    
    
      throw new IllegalArgumentException("name should not be empty.");
    }
    this.name = name;

    if (maxTotal != null) {
    
    
      if (maxTotal <= 0) {
    
    
        throw new IllegalArgumentException("maxTotal should be positive.");
      }
      this.maxTotal = maxTotal;
    }

    if (maxIdle != null) {
    
    
      if (maxIdle < 0) {
    
    
        throw new IllegalArgumentException("maxIdle should not be negative.");
      }
      this.maxIdle = maxIdle;
    }

    if (minIdle != null) {
    
    
      if (minIdle < 0) {
    
    
        throw new IllegalArgumentException("minIdle should not be negative.");
      }
      this.minIdle = minIdle;
    }
  }
  //...省略getter方法...
}

Agora, ResourcePoolConfig tem apenas 4 itens configuráveis, correspondentes ao construtor, existem apenas 4 parâmetros, e o número de parâmetros é pequeno. No entanto, se os itens configuráveis ​​aumentarem gradualmente e se tornarem 8, 10 ou até mais, continuar a seguir as ideias de design atuais, a lista de parâmetros do construtor ficará muito longa e o código será mais legível e fácil de usar. Vai piorar. Ao usar o construtor, é fácil entendermos mal a ordem dos parâmetros e passarmos os valores dos parâmetros errados, levando a bugs muito ocultos.


// 参数太多,导致可读性差、参数可能传递错误
ResourcePoolConfig config = new ResourcePoolConfig("dbconnectionpool", 16, null, 8, null, false , true, 10, 20falsetrue);

Você já deve ter pensado na solução para esse problema, que é usar a função set () para atribuir variáveis ​​de membro para substituir o construtor longo. Vamos examinar o código diretamente, conforme mostrado a seguir. Dentre eles, o nome do item de configuração é obrigatório, por isso o colocamos no construtor para configurá-lo, e deve ser preenchido ao criar um objeto de classe. Outros itens de configuração maxTotal, maxIdle, minIdle não são necessários, portanto, definimos por meio da função set (), permitindo que os usuários escolham preencher ou não preencher.


public class ResourcePoolConfig {
    
    
  private static final int DEFAULT_MAX_TOTAL = 8;
  private static final int DEFAULT_MAX_IDLE = 8;
  private static final int DEFAULT_MIN_IDLE = 0;

  private String name;
  private int maxTotal = DEFAULT_MAX_TOTAL;
  private int maxIdle = DEFAULT_MAX_IDLE;
  private int minIdle = DEFAULT_MIN_IDLE;
  
  public ResourcePoolConfig(String name) {
    
    
    if (StringUtils.isBlank(name)) {
    
    
      throw new IllegalArgumentException("name should not be empty.");
    }
    this.name = name;
  }

  public void setMaxTotal(int maxTotal) {
    
    
    if (maxTotal <= 0) {
    
    
      throw new IllegalArgumentException("maxTotal should be positive.");
    }
    this.maxTotal = maxTotal;
  }

  public void setMaxIdle(int maxIdle) {
    
    
    if (maxIdle < 0) {
    
    
      throw new IllegalArgumentException("maxIdle should not be negative.");
    }
    this.maxIdle = maxIdle;
  }

  public void setMinIdle(int minIdle) {
    
    
    if (minIdle < 0) {
    
    
      throw new IllegalArgumentException("minIdle should not be negative.");
    }
    this.minIdle = minIdle;
  }
  //...省略getter方法...
}

A seguir, vamos ver como usar a nova classe ResourcePoolConfig. Escrevi um código de amostra conforme mostrado abaixo. Sem longas chamadas de função e listas de parâmetros, o código é muito mais legível e utilizável.


// ResourcePoolConfig使用举例
ResourcePoolConfig config = new ResourcePoolConfig("dbconnectionpool");
config.setMaxTotal(16);
config.setMaxIdle(8);

Até agora, ainda não usamos o modo builder. Ao definir os itens necessários por meio do construtor e os itens de configuração opcionais por meio do método set (), podemos cumprir nossos requisitos de design. Se tornarmos o problema mais difícil, por exemplo, ainda precisamos resolver os três problemas a seguir, então as idéias de design atuais não podem ser satisfeitas.

● Como acabamos de mencionar, o nome é obrigatório, então o colocamos no construtor e o definimos ao criar um objeto à força. Se houver muitos itens de configuração obrigatórios, coloque esses itens de configuração obrigatórios no construtor, então o construtor terá uma longa lista de parâmetros. Se definirmos os itens obrigatórios por meio do método set (), a lógica de verificar se esses itens obrigatórios foram preenchidos está longe de ser colocada.

● Além disso, presume-se que haja certas dependências entre os itens de configuração. Por exemplo, se o usuário definir maxTotal, maxIdle e minIdle, os outros dois devem ser definidos explicitamente; ou há certas restrições entre os itens de configuração As condições, por exemplo, maxIdle e minIdle devem ser menores ou iguais a maxTotal. Se continuarmos a usar as ideias de design atuais, a lógica de verificação das dependências ou restrições entre esses itens de configuração não será mais colocada.

● Se quisermos que o objeto da classe ResourcePoolConfig seja um objeto imutável, ou seja, após a criação do objeto, os valores das propriedades internas não podem ser modificados. Para obter essa funcionalidade, não podemos expor o método set () na classe ResourcePoolConfig.

Para resolver esses problemas, o modo construtor é útil.


public class ResourcePoolConfig {
    
    
  private String name;
  private int maxTotal;
  private int maxIdle;
  private int minIdle;

  private ResourcePoolConfig(Builder builder) {
    
    
    this.name = builder.name;
    this.maxTotal = builder.maxTotal;
    this.maxIdle = builder.maxIdle;
    this.minIdle = builder.minIdle;
  }
  //...省略getter方法...

  //我们将Builder类设计成了ResourcePoolConfig的内部类。
  //我们也可以将Builder类设计成独立的非内部类ResourcePoolConfigBuilder。
  public static class Builder {
    
    
    private static final int DEFAULT_MAX_TOTAL = 8;
    private static final int DEFAULT_MAX_IDLE = 8;
    private static final int DEFAULT_MIN_IDLE = 0;

    private String name;
    private int maxTotal = DEFAULT_MAX_TOTAL;
    private int maxIdle = DEFAULT_MAX_IDLE;
    private int minIdle = DEFAULT_MIN_IDLE;

    public ResourcePoolConfig build() {
    
    
      // 校验逻辑放到这里来做,包括必填项校验、依赖关系校验、约束条件校验等
      if (StringUtils.isBlank(name)) {
    
    
        throw new IllegalArgumentException("...");
      }
      if (maxIdle > maxTotal) {
    
    
        throw new IllegalArgumentException("...");
      }
      if (minIdle > maxTotal || minIdle > maxIdle) {
    
    
        throw new IllegalArgumentException("...");
      }

      return new ResourcePoolConfig(this);
    }

    public Builder setName(String name) {
    
    
      if (StringUtils.isBlank(name)) {
    
    
        throw new IllegalArgumentException("...");
      }
      this.name = name;
      return this;
    }

    public Builder setMaxTotal(int maxTotal) {
    
    
      if (maxTotal <= 0) {
    
    
        throw new IllegalArgumentException("...");
      }
      this.maxTotal = maxTotal;
      return this;
    }

    public Builder setMaxIdle(int maxIdle) {
    
    
      if (maxIdle < 0) {
    
    
        throw new IllegalArgumentException("...");
      }
      this.maxIdle = maxIdle;
      return this;
    }

    public Builder setMinIdle(int minIdle) {
    
    
      if (minIdle < 0) {
    
    
        throw new IllegalArgumentException("...");
      }
      this.minIdle = minIdle;
      return this;
    }
  }
}

// 这段代码会抛出IllegalArgumentException,因为minIdle>maxIdle
ResourcePoolConfig config = new ResourcePoolConfig.Builder()
        .setName("dbconnectionpool")
        .setMaxTotal(16)
        .setMaxIdle(10)
        .setMinIdle(12)
        .build();

Na verdade, usar o modo construtor para criar um objeto também pode evitar o estado inválido do objeto. Deixe-me explicar com outro exemplo. Por exemplo, se definirmos uma classe retangular, se não usarmos o modo construtor e adotarmos o método de criar primeiro e depois definir, isso fará com que o objeto fique em um estado inválido após o primeiro conjunto. O código específico é o seguinte:


Rectangle r = new Rectange(); // r is invalid
r.setWidth(2); // r is invalid
r.setHeight(3); // r is valid

Para evitar a existência desse estado inválido, precisamos usar o construtor para inicializar todas as variáveis ​​de membro de uma vez. Se houver muitos parâmetros no construtor, precisamos considerar o uso do modo construtor, primeiro defina as variáveis ​​do construtor e, em seguida, crie o objeto uma vez, para que o objeto esteja sempre em um estado válido.

Na verdade, se não nos importamos se o objeto tem um estado inválido temporário, não nos importamos muito se o objeto é mutável. Por exemplo, se o objeto é usado apenas para mapear os dados lidos do banco de dados, então expomos diretamente o método set () para definir o valor da variável de membro da classe. Além disso, usando o modo builder para construir objetos, o código é na verdade um pouco repetitivo.As variáveis ​​de membro na classe ResourcePoolConfig devem ser redefinidas na classe Builder. Qual é a diferença com o modelo de fábrica?

Qual é a diferença com o modelo de fábrica?

A partir da explicação acima, podemos ver que o modo builder permite que a classe builder seja responsável pela criação do objeto. No padrão de fábrica mencionado na lição anterior, a classe de fábrica é responsável pela criação de objetos. Qual a diferença entre eles?

Na verdade, o padrão de fábrica é usado para criar tipos de objetos diferentes, mas relacionados (um grupo de subclasses que herdam a mesma classe ou interface pai), e os parâmetros fornecidos determinam qual tipo de objeto criar. O modo builder é usado para criar um tipo de objeto complexo, definindo diferentes parâmetros opcionais, "customizados" para criar diferentes objetos.

Existe um exemplo clássico na Internet que explica a diferença entre os dois.

Os clientes entram em um restaurante para fazer o pedido. Usamos o modelo de fábrica para fazer diferentes alimentos, como pizza, hambúrgueres e saladas, de acordo com as diferentes escolhas dos usuários. Para pizza, os usuários têm vários ingredientes para personalizar, como queijo, tomate e queijo. Usamos o modo builder para fazer pizza de acordo com os diferentes ingredientes selecionados pelo usuário.

Na verdade, não devemos ser muito acadêmicos. Temos que distinguir o modelo de fábrica e o modelo do construtor de forma tão clara. O que precisamos saber é por que cada modelo é projetado de tal forma e que problemas pode resolver. Somente entendendo essas coisas mais essenciais, podemos aplicar com flexibilidade sem copiá-las, e podemos até mesmo misturar vários modelos para criar novos modelos para resolver problemas em cenários específicos.

O construtor resolve principalmente os problemas de parâmetros excessivos, inspeção de parâmetros e imutabilidade de objetos de controle após a criação

Acho que você gosta

Origin blog.csdn.net/zhujiangtaotaise/article/details/110443556
Recomendado
Clasificación