《设计模式》- 创建型(单例模式、工厂模式、建造者模式、原型模式)

系列文章目录

《设计模式》-代码质量评价标准和设计原则
《设计模式》- 创建型(单例模式、工厂模式、建造者模式、原型模式)
《设计模式》- 结构型(代理模式、装饰者模式、适配器模式、桥接模式、门面模式、组合模式、亨元模式)



前言

创建型模式主要解决对象的创建问题,封装复杂的创建过程,解耦对象的创建代码和使用代码。

常用的:单例模式、工厂模式(工厂方法和抽象工厂)、建造者模式

不常用的:原型模式

一、单例模式

一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式。

3个要点

  1. 某个类只能有一个实例
  2. 它必须自行创建这个实例
  3. 它必须自行向整个系统提供这个实例

实现方式

饿汉式

当类被加载时,静态变量instance会被初始化,此时类的私有构造函数会被调用,单例类的唯一实例将被创建。如果使用饿汉式单例来实现ID生成器IdGenerator类的设计,则不会出现创建多个单例对象的情况,可确保单例对象的唯一性。

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

懒汉式

懒汉式单例在第一次调用getInstance()方法时实例化,在类加载时并不自行实例化,这种技术又称为延迟加载(Lazy Load)技术,即需要的时候再加载实例。为了避免多个线程同时调用getInstance()方法,可以使用关键字synchronized。

该懒汉式单例类在getInstance()方法前面增加了关键字synchronized进行线程锁定,以处理多个线程同时访问的问题。上述代码虽然解决了线程安全问题,但是每次调用getInstance()时都需要进行线程锁定判断,在多线程高并发访问环境中,将会导致系统性能大大降低。

public class IdGenerator { 
private AtomicLong id = new AtomicLong(0);
private static IdGenerator instance;
private IdGenerator() {}
public static synchronized IdGenerator getInstance() {
  if (instance == null) {
    instance = new IdGenerator();
  }
  return instance;
}
public long getId() { 
  return id.incrementAndGet();
}
}

双重检测

需要两次判空的原因:假如某一瞬间线程A和线程B都在调用getInstance()方法,此时instance对象为null值,均能通过“instance==null”的判断。由于实现了synchronized加锁机制,线程A进入synchronized锁定的代码中执行实例创建代码,线程B处于排队等待状态,必须等待线程A执行完毕后才可以进入synchronized锁定代码。但当A执行完毕时,线程B并不知道实例已经创建,将继续创建新的实例,导致产生多个单例对象,违背单例模式的设计思想。因此需要进行进一步改进,在synchronized锁定代码中再进行一次“instance==null”判断,这种方式称为双重检查锁定(Double-Check Locking)

如果使用双重检查锁定来实现懒汉式单例类,需要在静态成员变量instance之前增加修饰符volatile,被volatile修饰的成员变量可以确保多个线程都能够正确处理,且该代码只能在JDK 1.5及以上版本中才能正确执行。由于volatile关键字会屏蔽Java虚拟机所做的一些代码优化,可能会导致系统运行效率降低

public class IdGenerator { 
private AtomicLong id = new AtomicLong(0);
private volatile static IdGenerator instance;
private IdGenerator() {}
public static IdGenerator getInstance() {
  if (instance == null) {
    synchronized(IdGenerator.class) { // 此处为类级别的锁
      if (instance == null) {
        instance = new IdGenerator();
      }
    }
  }
  return instance;
}
public long getId() { 
  return id.incrementAndGet();
}
}

静态内部类

由于静态单例对象没有作为IdGenerator的成员变量直接实例化,因此类加载时不会实例化IdGenerator。第一次调用getInstance()时将加载内部类SingletonHolder,在该内部类中定义了一个static类型的变量instance,此时会首先初始化这个成员变量,由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。由于getInstance()方法没有被任何线程锁定,因此其性能不会造成任何影响。

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

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

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

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

枚举

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

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

适用场景

  1. 系统只需要一个实例对象。例如,系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象。
  2. 客户调用类的单个实例只允许使用一个公共访问点。除了该公共访问点,不能通过其他途径访问该实例。

二、工厂模式

工厂模式是最常用的一类创建型设计模式。通常所说的工厂模式是指工厂方法模式,它也是使用频率最高的工厂模式。

1、简单工厂

定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。因为在简单工厂模式中用于创建实例的方法是静态(static)方法,因此简单工厂模式又被称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。

要点

当你需要什么,只需要传入一个正确的参数,就可以获取你所需要的对象,而无须知道其创建的细节。

实现方式

方法一:

 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;
  }

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

 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;
  }
}

核心内容

  1. RuleConfigParserFactory(工厂角色):即工厂类,它是简单工厂模式的核心,负责实现创建所有产品实例的内部逻辑。工厂类可以被外界直接调用,创建所需的产品对象。在工厂类中提供了静态的工厂方法createParser(),它的返回类型为抽象产品类型IRuleConfigParser。
  2. IRuleConfigParser(抽象产品角色):它是工厂类所创建的所有对象的父类或者接口,封装了各种产品对象的公有方法。抽象产品的引入将提高系统的灵活性,使得在工厂类中只需定义一个通用的工厂方法,因为所有创建的具体产品对象都是其子类对象或者其实现类对象。
  3. JsonRuleConfigParser(具体产品角色):它是简单工厂模式的创建目标,所有被创建的对象都充当这个角色的某个具体类的实例。每个具体产品角色都继承了或者实现了抽象产品角色,需要实现在抽象产品中声明的抽象方法或者接口。

适用场景

  1. 工厂类负责创建的对象比较少。由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。
  2. 客户端只知道传入工厂类的参数,对于如何创建对象并不关心。

2、工厂方法

定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。工厂方法模式又简称为工厂模式(Factory Pattern),又可称作虚拟构造器模式(Virtual Constructor Pattern)或多态工厂模式(Polymorphic FactoryPattern)。工厂方法模式是一种类创建型模式。

实现方式

 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();
  }
}

简单工厂创建工厂类对象

 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;
  }
}

核心内容

  1. IRuleConfigParser(抽象产品):它是定义产品的接口,是工厂方法模式所创建对象的超类型,也就是产品对象的公共父类。
  2. JsonRuleConfigParser(具体产品):它实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,具体工厂和具体产品之间一一对应。
  3. IRuleConfigParserFactory(抽象工厂):在抽象工厂类中,声明了工厂方法(createParser),用于返回一个产品。抽象工厂是工厂方法模式的核心,所有创建对象的工厂类都必须实现该接口。
  4. JsonRuleConfigParserFactory(具体工厂):它是抽象工厂类的子类,实现了抽象工厂中定义的工厂方法,并可由客户端调用,返回一个具体产品类的实例。

适用场景

  1. 客户端不知道其所需要的对象的类。在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建,可将具体工厂类的类名存储在配置文件或数据库中。
  2. 抽象工厂类通过其子类来指定创建哪个对象。在工厂方法模式中,抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。

3、抽象工厂

提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,它是一种对象创建型模式。

实现方式

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代码

核心内容

  1. IConfigParserFactory(抽象工厂):它声明了一组用于创建一族产品的方法,每个方法对应一种产品。
  2. JsonConfigParserFactory(具体工厂):它实现了在抽象工厂中声明的创建产品的方法,生成一组具体产品,这些产品构成了一个产品族,每种产品都位于某个产品等级结构中。
  3. IRuleConfigParser(抽象产品):它为每种产品声明接口,在抽象产品中声明了产品所具有的业务方法。
  4. JsonRuleConfigParser(具体产品):它定义具体工厂生产的具体产品对象,实现在抽象产品接口中声明的业务方法。

适用场景

  1. 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是很重要的,用户无须关心对象的创建过程,将对象的创建和使用解耦。
  2. 系统中有多于一个的产品族,而每次只使用其中某一个产品族。可以通过配置文件等方式来使得用户可以动态改变产品族,也可以很方便地增加新的产品族。
  3. 属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。同一个产品族中的产品可以是没有任何关系的对象,但是它们都具有一些共同的约束。例如同一操作系统下的按钮和文本框,按钮与文本框之间没有直接关系,但它们都是属于某一操作系统的,此时具有一个共同的约束条件:操作系统的类型。
  4. 产品等级结构稳定,设计完成之后,不会向系统中增加新的产品等级结构或者删除已有的产品等级结构。

三、 建造者模式

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式是一种对象创建型模式。

实现方式

 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();

适用场景

  1. 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员变量。
  2. 需要生成的产品对象的属性相互依赖,需要指定其生成顺序。
  3. 对象的创建过程独立于创建该对象的类。在建造者模式中通过引入指挥者类,将创建过程封装在指挥者类中,而不在建造者类和客户类中。
  4. 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。

四、原型模式

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

实现方式

 public class ConcretePrototype extends Prototype {
private String attr;//成员变量

public void setAttr(String attr) {
this.attr = attr;
}
public String getAttr(){
return this.attr;
}
//克隆方法
public Prototype clone() {
Prototype prototype = new ConcretePrototype();//创建新对象
prototype.setAttr(this.attr);
return prototype;
}
}

浅克隆

在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。

深克隆

在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。

适用场景

  1. 创建新对象成本较大(例如初始化需要占用较长的时间,占用太多的CPU资源或网络资源)。新的对象可以通过原型模式对已有对象进行复制来获得,如果是相似对象,则可以对其成员变量稍作修改。
  2. 如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占用内存较少时,可以使用原型模式配合备忘录模式来实现。
  3. 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态。通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值