计算机编程

23种模式 - 创建型

2022-04-18  本文已影响0人  Zeppelin421

创建型模式主要解决对象的创建问题,封装复杂的创建过程,解耦对象的创建代码和使用代码。
单例模式:用来创建全局唯一的对象。
工厂模式:用来创建不同但是相关类型的对象(继承同一父类或接口的一组子类),由给定的参数来决定创建哪种类型的对象
建造者模式:用来创建复杂对象,可以通过设置不同的可选参数,“定制化”地创建不同的对象
原型模式:针对创建成本比较大的对象,利用对已有对象复制的方式进行创建,以达到节省创建时间的目的

单例模式

单例设计模式(Singleton Design Pattern):一个类只允许创建一个对象(或实例),那这个类就是一个单例类,这种设计模式就叫做单例模式

实现

public class IdGenerator {
    private AtomicLong id = new AtomicLong(0);

    private static final IdGenerator instance = new IdGenerator();

    public static IdGenerator getInstance() { return instance; }

    private IdGenerator() {}

    public long getId() {
        return id.incrementAndGet();
    }
}
public class IdGenerator {
    private AtomicLong id = new AtomicLong(0);

    private static final IdGenerator instance = null;

    public static synchronized IdGenerator getInstance() {
        if (instance == null) {
            instance = new IdGenerator();
        }
        return instance;
    }

    private IdGenerator() {}

    public long getId() {
        return id.incrementAndGet();
    }
}
public class IdGenerator {
    private AtomicLong id = new AtomicLong(0);

    private static final IdGenerator instance = null;

    public static IdGenerator getInstance() {
        if (instance == null) {
            synchronized(IdGenerator.class) {
                if (instance == null) {
                    instance = new IdGenerator();
                }           
            }
        }
        return instance;
    }

    private IdGenerator() {}

    public long getId() {
        return id.incrementAndGet();
    }
}
public class IdGenerator {

    private AtomicLong id = new AtomicLong(0);

    private static class SingletonHolder {
        private static final IdGenerator instance = new IdGenerator();
    }

    public static IdGenerator getInstance() {
        return SingletonHolder.instance;
    }

    private IdGenerator() {}

    public long getId() {
        return id.incrementAndGet();
    }
}
public enum IdGenerator {
    INSTANCE;

    private AtomicLong id = new AtomicLong(0);

    public long getId() {
        return id.incrementAndGet();
    }
}

存在问题

替代方案

如何理解单例模式中的唯一性

定义中提到“一个类只允许创建唯一一个对象”,那对象的唯一性的作用范围是什么呢?单例模式创建的对象是进程唯一的。

如何实现线程唯一的单例

通过一个HashMap来存储对象,key是线程ID,value是对象。例如Java语言本身提供的ThreadLocal工具类

如何实现集群环境下的单例

需要把单例对象序列化并存储到外部共享存储区(比如文件)。进程在使用这个单例对象的时候,需要先从外部共享存储区中将它读取到内存,并反序列化成对象,然后再使用,使用完成之后还需要再存储回外部共享存储区。
为了保证任何时刻,在进程间都只有一份对象存在,一个进程在获取对象之后,需要对对象加锁,避免其他进程再将其获取。

如何实现一个多例模式

“单例”指的是一个类只能创建一个对象。“多例”指的就是一个类可以创建多个对象,但个数是有限的。多例的实现也比较简单,通过一个Map来存储对象类型和对象之间的对应关系,来控制对象的个数。

工厂模式

工厂模式(Factory Design Pattern)是最常用的实例化对象模式,是用工厂方法代替 new 操作的一种模式。在创建对象时,不会对客户端暴露对象的创建逻辑,而是通过使用共同的接口来创建对象。

简单工厂(Simple Factory)

简单工厂模式中创建实例的方法通常为静态方法,因此简单工厂模式又叫做静态工厂方法模式

public class RuleConfigSource {
  public RuleConfig load(String ruleConfigFilePath) {
    String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
    IRuleConfigParser parser = null;
    if ("json".equalsIgnoreCase(ruleConfigFileExtension)) {
      parser = new JsonRuleConfigParser();
    } else if ("xml".equalsIgnoreCase(ruleConfigFileExtension)) {
      parser = new XmlRuleConfigParser();
    } else if ("yaml".equalsIgnoreCase(ruleConfigFileExtension)) {
      parser = new YamlRuleConfigParser();
    } else if ("properties".equalsIgnoreCase(ruleConfigFileExtension)) {
      parser = new PropertiesRuleConfigParser();
    } else {
      throw new InvalidRuleConfigException(
             "Rule config file format is not supported: " + ruleConfigFilePath);
    }

    String configText = "";
    //从ruleConfigFilePath文件中读取配置文本到configText中
    RuleConfig ruleConfig = parser.parse(configText);
    return ruleConfig;
  }

  private String getFileExtension(String filePath) {
    //...解析文件名获取扩展名,比如rule.json,返回json
    return "json";
  }
}

// 1、为了让代码逻辑更加清晰,可读性更好,要善于将功能独立的代码封装成函数
// 2、为了让类的职责更加单一、代码更加清晰,可以将独立的函数剥离到一个单独的类中

public class RuleConfigSource {
  public RuleConfig load(String ruleConfigFilePath) {
    String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
    IRuleConfigParser parser = RuleConfigParserFactory.createParser(ruleConfigFileExtension);
    if (parser == null) {
      throw new InvalidRuleConfigException(
              "Rule config file format is not supported: " + ruleConfigFilePath);
    }

    String configText = "";
    //从ruleConfigFilePath文件中读取配置文本到configText中
    RuleConfig ruleConfig = parser.parse(configText);
    return ruleConfig;
  }
}

// 第一种简单工厂模式
public class RuleConfigParserFactory {
  public static IRuleConfigParser createParser(String configFormat) {
    IRuleConfigParser parser = null;
    if ("json".equalsIgnoreCase(configFormat)) {
      parser = new JsonRuleConfigParser();
    } else if ("xml".equalsIgnoreCase(configFormat)) {
      parser = new XmlRuleConfigParser();
    } else if ("yaml".equalsIgnoreCase(configFormat)) {
      parser = new YamlRuleConfigParser();
    } else if ("properties".equalsIgnoreCase(configFormat)) {
      parser = new PropertiesRuleConfigParser();
    }
    return parser;
  }
}

// 第二种简单工厂模式
public class RuleConfigParserFactory {
  private static final Map<String, RuleConfigParser> cachedParsers = new HashMap<>();

  static {
    cachedParsers.put("json", new JsonRuleConfigParser());
    cachedParsers.put("xml", new XmlRuleConfigParser());
    cachedParsers.put("yaml", new YamlRuleConfigParser());
    cachedParsers.put("properties", new PropertiesRuleConfigParser());
  }

  public static IRuleConfigParser createParser(String configFormat) {
    if (configFormat == null || configFormat.isEmpty()) {
      return null;//返回null还是IllegalArgumentException全凭你自己说了算
    }
    IRuleConfigParser parser = cachedParsers.get(configFormat.toLowerCase());
    return parser;
  }
}

简单工程模式的代码实现中,有多处 if 分支判断逻辑,违背开闭原则,但权衡扩展性和可读性,这样的代码在大多数情况下(不需要频繁添加)是没有问题的。

工厂方法(Factory Method)

工厂方法模式是对简单工厂模式的进一步抽象化,其好处是可以使系统在不修改原来代码的情况下引进新的产品,即满足开闭原则

public class RuleConfigSource {
  public RuleConfig load(String ruleConfigFilePath) {
    String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);

    IRuleConfigParserFactory parserFactory = RuleConfigParserFactoryMap.getParserFactory(ruleConfigFileExtension);
    if (parserFactory == null) {
      throw new InvalidRuleConfigException("Rule config file format is not supported: " + ruleConfigFilePath);
    }
    IRuleConfigParser parser = parserFactory.createParser();

    String configText = "";
    //从ruleConfigFilePath文件中读取配置文本到configText中
    RuleConfig ruleConfig = parser.parse(configText);
    return ruleConfig;
  }

  private String getFileExtension(String filePath) {
    //...解析文件名获取扩展名,比如rule.json,返回json
    return "json";
  }
}

//因为工厂类只包含方法,不包含成员变量,完全可以复用,
//不需要每次都创建新的工厂类对象,所以,简单工厂模式的第二种实现思路更加合适。
public class RuleConfigParserFactoryMap { //工厂的工厂
  private static final Map<String, IRuleConfigParserFactory> cachedFactories = new HashMap<>();

  static {
    cachedFactories.put("json", new JsonRuleConfigParserFactory());
    cachedFactories.put("xml", new XmlRuleConfigParserFactory());
    cachedFactories.put("yaml", new YamlRuleConfigParserFactory());
    cachedFactories.put("properties", new PropertiesRuleConfigParserFactory());
  }

  public static IRuleConfigParserFactory getParserFactory(String type) {
    if (type == null || type.isEmpty()) {
      return null;
    }
    IRuleConfigParserFactory parserFactory = cachedFactories.get(type.toLowerCase());
    return parserFactory;
  }
}

public interface IRuleConfigParserFactory {
  IRuleConfigParser createParser();
}

public class JsonRuleConfigParserFactory implements IRuleConfigParserFactory {
  @Override
  public IRuleConfigParser createParser() {
    return new JsonRuleConfigParser();
  }
}

public class XmlRuleConfigParserFactory implements IRuleConfigParserFactory {
  @Override
  public IRuleConfigParser createParser() {
    return new XmlRuleConfigParser();
  }
}

public class YamlRuleConfigParserFactory implements IRuleConfigParserFactory {
  @Override
  public IRuleConfigParser createParser() {
    return new YamlRuleConfigParser();
  }
}

public class PropertiesRuleConfigParserFactory implements IRuleConfigParserFactory {
  @Override
  public IRuleConfigParser createParser() {
    return new PropertiesRuleConfigParser();
  }
}

对于当前场景来说,工厂模式需要额外创建诸多 Factory 类,也会增加代码的复杂性,而且每个 Factory 类只是做简单的 new 操作,功能非常单薄,也没有必要设计成独立的类,所以当前场景下,简单工厂模式简单好用,比工厂模式更加合适。

什么时候该用工厂方法模式,而非简单工厂模式

抽象工厂(Abstract Factory)

在简单工厂和工厂方法中,类只有一种分类方式。但如果类有两种分类方式,比如既可以按照配置文件格式来分类,也可以按照解析的对象来分类,那就会对应8个 parser 类。如果未来还需要增加针对业务配置的解析器,过多的类会导致系统难以维护。此时抽象工厂就诞生了,它可以让一个工厂负责创建多个不同类型的对象,可以有效减少工厂类的个数。

public interface IConfigParserFactory {
  IRuleConfigParser createRuleParser();
  ISystemConfigParser createSystemParser();
  //此处可以扩展新的parser类型,比如IBizConfigParser
}

public class JsonConfigParserFactory implements IConfigParserFactory {
  @Override
  public IRuleConfigParser createRuleParser() {
    return new JsonRuleConfigParser();
  }

  @Override
  public ISystemConfigParser createSystemParser() {
    return new JsonSystemConfigParser();
  }
}

public class XmlConfigParserFactory implements IConfigParserFactory {
  @Override
  public IRuleConfigParser createRuleParser() {
    return new XmlRuleConfigParser();
  }

  @Override
  public ISystemConfigParser createSystemParser() {
    return new XmlSystemConfigParser();
  }
}

// 省略YamlConfigParserFactory和PropertiesConfigParserFactory代码

总结

当创建逻辑比较复杂,是一个“大工程”的时候,就需要考虑使用工厂模式,封装对象的创建过程,将对象的创建和使用相分离。两种创建逻辑比较复杂的情况:

上升到思维层面来看工厂模式,可以从四个方面判断要不要使用工厂模式:

建造者模式

建造者模式(Builder Design Pattern、构建者模式、生成器模式)是设计模式的一种,将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

为什么需要建造者模式

如果一个类中有很多属性,为了避免构造函数的参数列表过长,影响代码的可读性和易用性,可以通过构造函数配合 set 方法来解决。
如果存在以下几种情况,就需要考虑使用构造者模式

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方法...
}

// 采用构建者模式重构
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();

与工厂模式有何区别

工厂模式是用来创建不同但是相关类型的对象(继承同一父类或接口的一组子类),由给定的参数决定创建哪种类型的对象。
构造者模式是用来创建一种类型的复杂对象,通过设置不同的可选参数,“定制化”地创建不同的对象

顾客走进一家餐厅点餐,利用工厂模式,根据用户不同的选择,来制作不同的食物,比如披萨、汉堡、沙拉。对于披萨来说,用户又有各种配料可以定制,比如奶酪、西红柿、起司,通过构造者模式根据用户选择的不同配料来制作披萨

只有了解这些最本质的东西,才能不生搬硬套,才能灵活应用,甚至可以混用各种模式创造出新的模式,来解决特定场景的问题

原型模式

如果对象的创建成本比较大,而同一个类的不同对象之间差别不大,这种情况下,可以利用对已有对象(原型)进行复制(或拷贝)的方式来创建新对象,已达到节省创建时间的目的。这种基于原型来创建对象的方式就叫作原型设计模式(Prototype Design Pattern)

何为“对象的创建成本比较大”

创建对象包含的申请内存、给成员变量赋值这一过程,本身并不会花费太多时间,或者说对于大部分业务系统来说,这点时间完全是可以忽略的。应用一个复杂的模式,只得到一点点的性能提升,这就是所谓的过度设计,得不偿失。

如果对象中的数据需要经过复杂的计算才能得到,或者需要从RPC、网络、数据库、文件系统等非常慢的 IO 中读取,这种情况下,就可以利用原型模型,从其他已有对象中直接拷贝得到,而不用每次在创建新对象的时候,都重复执行这些耗时的操作。

原型模型的实现方式:深拷贝和浅拷贝

private HashMap currentKeywords=new HashMap<>();
// 原型模式就这么简单,拷贝已有对象的数据,更新少量差值 
HashMap newKeywords = (HashMap) currentKeywords.clone();

浅拷贝和深拷贝的区别在于,浅拷贝只会复制图中的索引(散列表),不会复制数据(SearchWord对象)本身。相反,深拷贝不仅仅会复制索引,还会复制数据本身。浅拷贝得到的对象(newKeywords)跟原始对象(currentKeywords)共享数据(SearchWord对象),而深拷贝得到的是一份完完全全独立的对象


浅拷贝
深拷贝

Java语言中,Object类的 clone() 方法执行的就是浅拷贝。它只会拷贝对象中的基本数据类型的数据(int,long),以及引用对象(SearchWord)的内存地址,不会递归地拷贝引用对象本身。

如何深拷贝

针对HashMap这种新老替换的数据结构,可以先采用浅拷贝的方式创建 newKeywords。对于需要更新的 SearchWord 对象,再使用深拷贝的方式创建一份新的对象,替换 newKeywords 中的老对象。这种方式即利用了浅拷贝节省时间、空间的优点,又能保证 currentKeywords 中的数据都是老版本的。

上一篇 下一篇

猜你喜欢

热点阅读