设计模式篇(二):一文搞懂创建型模式

文章需要耐心学习理解

单例模式

单例模式属于创建型模式,所谓类的单例设计模式,就是采用一定的方法保证在整个的软件系统中,对某个类只能存在唯一的实例,并且该类只提供一个取得其对象实例的方法(静态方法)

比如Mybatis的SessionFactory,它充当数据存储源的代理,并负责创建Session对象。SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个SessionFactory就够了,这时就会使用到单例模式,那单例模式怎么使用?如何实现?下面会详细解释

饿汉模式

实现步骤:

  • 构造器私有化(防止new)
  • 类的内部创建对象
  • 向外部暴露一个静态的公共方法
  • 代码实现
java 复制代码
public class SingletonTest01 {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        System.out.println(instance==instance1);
    }
}

class Singleton {

    //1.设置私有构造方法,这就堵死了外界利用new关键字创建此类实例的可能
    private Singleton(){

    }

    //2.本类内部创建对象实例
    private static final Singleton SINGLETON = new Singleton();


    //3.对外提供一个公有的静态方法,返回实例对象
    public static Singleton getInstance(){
        return SINGLETON;
    }


}

所谓饿汉模式,就是采用静态常量提前在编译期间创建好对象,java中的常量是唯一存在的,在类中提供getinstance获取对象即可,由于是在编译器提前创建,并不具备延迟加载(用时创建),因此称为懒汉模式。虽然不具备延迟加载,但是我认为延迟加载不一定就好,如果一个对象创建较为复杂耗时,那将创建对象提前到编译期间而不是用时创建,其实是好的选择

懒汉模式

java 复制代码
    public class SingletonTest02 {
        public static void main(String[] args) {
            System.out.println("懒汉式,此种方法线程不安全");
            Singleton singleton = Singleton.getInstance();
            Singleton singleton1 = Singleton.getInstance();
            System.out.println(singleton == singleton1);
    
        }
    }
    
    class Singleton {
    
        private static Singleton singleton;
    
        private Singleton() {
        }
    
        public static Singleton getInstance() {
            if (singleton == null) {
                singleton = new Singleton();
            }
            return singleton;
        }
    } 

懒汉模式相比饿汉模式来说,支持延迟加载,在getinstance的调用时才会创建对象使用,但是上面的实现是线程不安全的,下面实现线程安全的

java 复制代码
  public class SingletonTest03 {
        public static void main(String[] args) {
            System.out.println("懒汉式,线程不安全");
            Singleton singleton = Singleton.getInstance();
            Singleton singleton1 = Singleton.getInstance();
            System.out.println(singleton==singleton1);
        }
    }
    
    class Singleton {
    
        private static Singleton singleton;
    
        private Singleton() {
        }
    
        //加入同步处理的代码
        public static synchronized Singleton getInstance() {
            if (singleton == null) {
                singleton = new Singleton();
            }
            return singleton;
        }
    }

线程安全的是通过加锁实现的,synchronized加在静态方法上,对单例类进行加锁,使每个进来的线程都能看到这把锁,但是这样会导致不必要的锁竞争,效率低,实际上懒汉模式还有一种线程安全的写法

双重检测

java 复制代码
  public class SingletonTest04 {
      public static void main(String[] args) {
          System.out.println("懒汉式,线程不安全");
          Singleton singleton = Singleton.getInstance();
          Singleton singleton1 = Singleton.getInstance();
          System.out.println(singleton==singleton1);
      }
  }
  
  class Singleton {
  
      private static volatile Singleton singleton;
  
      private Singleton() {
      }
  
      public static  Singleton getInstance() {
          if (singleton == null) {
              synchronized (Singleton.class){
                  if (singleton==null){
                      singleton = new Singleton();
                  }
              }
          }
          return singleton;
      }
  }

这样提高代码效率,虽然也是加了类锁,但是减少了线程之间的竞争。因此双重检测的写法较为推荐

枚举模式

由于java的枚举的特性,天生支持单例模式,下面举个例子

java 复制代码
public enum Singleton {
    INSTANCE;
}

使用时,只需要Singleton.INSTANCE就可以了

静态内部类

我们再来看一种比双重检测更加简单的实现方法,那就是利用 Java 的静态内部类。它有点类似饿汉式,但又能做到了延迟加载。具体是怎么做到的呢?

java 复制代码
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();
    }
}

这是一个id生成器的例子,其中SingletonHolder是静态内部类,当调用getInstance方法是,使用到静态内部类时才会去创建使用,而一旦调用静态内部类就会去创建IdGenerator对象,实现了不需要加锁且延迟加载的单例模式

工厂模式

工厂模式属于创建型模式,一共有三种,分别是:简单工厂模式,方法工厂模式以及抽象工厂模式

简单工厂模式

先设计一个场景,我们需要一个配置加载器,根据传递参数不同,加载不同的参数解析器,比如json,xml等。看下代码

java 复制代码
private 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;
}

这是原本的样子,封装在一个方法中,通过闯入参数不同去创建不同参数解析器返回,但是为了让一个类功能更加单一,我们还可以进一步将 createParser() 函数剥离到一个独立的类中,让这个类只负责对象的创建。而这个类就是我们现在要讲的简单工厂模式类,看下面代码

java 复制代码
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;
}
}

这样就是简单工厂模式了,在上面的代码实现中,我们每次调用createParser() 的时候,都要创建一个新的 parser。实际上,如果 parser 可以复用,为了节省内存和对象创建的时间,我们可以将 parser 事先创建好缓存起来。当调用 createParser() 函数的时候,我们从缓存中取出 parser 对象直接使用

这有点类似单例模式和简单工厂模式的结合,具体的代码实现如下所示。在接下来的讲解中,我们把上一种实现方法叫作简单工厂模式的第一种实现方法,把下面这种实现方法叫作简单工厂模式的第二种实现方法

java 复制代码
public class RuleConfigParserFactory {
    private static final Map<String, RuleConfigParser> cachedParsers = new HashMa
    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 分支判断逻辑,违背开闭原则,但权衡扩展性和可读性,这样的代码实现在大多数情况下(比如,不需要频繁地添加 parser,也没有太多的 parser)是没有问题的

工厂方法

如果我们非得要将 if 分支逻辑去掉,那该怎么办呢?比较经典处理方法就是利用多态。按照多态的实现思路,对上面的代码进行重构。重构之后的代码如下所示

java 复制代码
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 IRuleConfigParserFact
@Override
public IRuleConfigParser createParser() {
return new PropertiesRuleConfigParser();
}
}

利用多态,当需要增加一种配置解析器时,只需要增加一个实现类即可,所以,工厂方法模式比起简单工厂模式更加符合开闭原则,但是这样的话当使用对于解析器时就复杂很多,看下面代码:

java 复制代码
if ("json".equalsIgnoreCase(ruleConfigFileExtension)) {
parserFactory = new JsonRuleConfigParserFactory();
} else if ("xml".equalsIgnoreCase(ruleConfigFileExtension)) {
parserFactory = new XmlRuleConfigParserFactory();
} else if ("yaml".equalsIgnoreCase(ruleConfigFileExtension)) {
parserFactory = new YamlRuleConfigParserFactory();
} else if ("properties".equalsIgnoreCase(ruleConfigFileExtension)) {
parserFactory = new PropertiesRuleConfigParserFactory();
} else {
throw new InvalidRuleConfigException("Rule config file format is not supp
}
IRuleConfigParser parser = parserFactory.createParser();

在使用时依旧是通过if else来判断使用的,那么我们可不可以改造成使用上面的方法,使用map缓存起来,使用时取走的方式呢?实际是可以的,我们需要创建工厂的工厂,也就是对工厂方法模式在套一个简单工厂模式,看代码:

java 复制代码
public class RuleConfigParserFactoryMap { //工厂的工厂
private static final Map<String, IRuleConfigParserFactory> cachedFactories =
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.toLowerCa
return parserFactory;
}
}

这样就简化很多

抽象工厂

讲完了简单工厂、工厂方法,我们再来看抽象工厂模式。抽象工厂模式的应用场景比较特殊,没有前两种常用,只需要了解下就行

解析器类只会根据配置文件格式(Json、Xml、Yaml......)来分类。但是,如果类有两种分类方 式,比如,我们既可以按照配置文件格式来分类,也可以按照解析的对象(Rule 规则配置还是 System 系统配置)来分类,那就会对应下面这 8 个 parser 类

java 复制代码
针对规则配置的解析器:基于接口IRuleConfigParser
JsonRuleConfigParser
XmlRuleConfigParser
YamlRuleConfigParser
PropertiesRuleConfigParser
针对系统配置的解析器:基于接口ISystemConfigParser
JsonSystemConfigParser
XmlSystemConfigParser
YamlSystemConfigParser
PropertiesSystemConfigParser

针对这种特殊的场景,如果还是继续用工厂方法来实现的话,我们要针对每个 parser 都编写一个工厂类,也就是要编写 8 个工厂类。如果我们未来还需要增加针对业务配置的解析器(比如 IBizConfigParser),那就要再对应地增加 4 个工厂类。而我们知道,过多的类也会让系统难维护。这个问题该怎么解决呢?

抽象工厂就是针对这种非常特殊的场景而诞生的。我们可以让一个工厂负责创建多个不同类型的对象(IRuleConfigParser、ISystemConfigParser 等),而不是只创建一种 parser 对象。这样就可以有效地减少工厂类的个数。具体的代码实现如下所示:

java 复制代码
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();
}
}

建造者模式

主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定

上面是官方的解释,举个例子,就是创建一个对象时,我们可能会在构造方法中写入参数,但是当一个对象的创建需要很多参数,且参数有必要的参数,也有非必要的参数,那继续沿用现在的设计思路,构造函数的参数列表会变得很长,代码在可读性和易用性上都会变差。在使用构造函数的时候,我们就容易搞错各参数的顺序,传递进错误的参数值,导致非常隐蔽的 bug,所以我们使用建造者模式来解决这个问题

接下来我们使用资源池的例子来解释建造者模式,直接看代码理解:

java 复制代码
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();

在ResourcePoolConfig资源池类中,将构造方法私有,不允许外界直接创建对象,同时参数改为Builder对象,Builder是内部类,本身具有无参构造方法Builder(),还提供了build()方法,来将自身参数赋值给ResourcePoolConfig对象,并且创建ResourcePoolConfig对象返回

总结

总结下,上面就是创建型模式的单例,工厂,建造模式,当然创建型模式还有原型模式,但是原型模式并不常用,在java里几乎不使用,反而是在js中比较常见,因此在这里就不介绍了,文章需要各位理解活学活用,如果文章写的某些地方不对,望各位大佬评论指正

相关推荐
用户37215742613522 分钟前
Java 实现 Excel 与 TXT 文本高效互转
java
浮游本尊1 小时前
Java学习第22天 - 云原生与容器化
java
渣哥3 小时前
原来 Java 里线程安全集合有这么多种
java
间彧3 小时前
Spring Boot集成Spring Security完整指南
java
间彧4 小时前
Spring Secutiy基本原理及工作流程
java
数据智能老司机4 小时前
精通 Python 设计模式——创建型设计模式
python·设计模式·架构
Java水解5 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
数据智能老司机5 小时前
精通 Python 设计模式——SOLID 原则
python·设计模式·架构
洛小豆7 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学7 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端