94 设计模式(极客)

1.单例

创建一个单例

//懒汉模式 + synchronized同步锁 + double-check

public final class Singleton {
    private volatile static Singleton instance= null;//不实例化
    public List<String> list = null;//list属性
    private Singleton(){
      list = new ArrayList<String>();
    }//构造函数
    public static Singleton getInstance(){//加同步锁,通过该函数向整个系统提供实例
        if(null == instance){//第一次判断,当instance为null时,则实例化对象,否则直接返回对象
          synchronized (Singleton.class){//同步锁
             if(null == instance){//第二次判断
                instance = new Singleton();//实例化对象
             }
          } 
        }
        return instance;//返回已存在的对象
    }
}

在多线程场景下,JVM 会保证只有一个线程能执行该类的 clinit(static 修饰的成员变量)方法,其它线程将会被阻塞等待。这种方式可以保证内存的可见性、顺序性以及原子性。

//懒汉模式 内部类实现

public final class Singleton {
  public List<String> list = null;// list属性

  private Singleton() {//构造函数
    list = new ArrayList<String>();
  }

  // 内部类实现
  public static class InnerSingleton {
    private static Singleton instance=new Singleton();//自行创建实例
  }

  public static Singleton getInstance() {
    return InnerSingleton.instance;// 返回内部类中的静态变量
  }
}

为什么单例:
在这里插入图片描述
我们将 Logger 设计成一个单例类,程序中只允许创建一个 Logger 对象,所有的线程共享使用的这一个 Logger 对象,共享一个 FileWriter 对象,而 FileWriter 本身是对象级别线程安全的,也就避免了多线程情况下写日志会互相覆盖的问题。

有些数据在系统中只应保存一份,那就比较适合设计为单例类。比如,配置信息类,连接池类、ID 生成器类

通过枚举实现单例:

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

单例类中对象的唯一性的作用范围是“进程唯一”的。
实现线程唯一:
我们通过一个 HashMap 来存储对象,其中 key 是线程 ID,value 是对象。这样我们就可以做到,不同的线程对应不同的对象,同一个线程只能对应一个对象。
实现集群唯一:
我们需要把这个单例对象序列化并存储到外部共享存储区(比如文件)。进程在使用这个单例对象的时候,需要先从外部共享存储区中将它读取到内存,并反序列化成对象,然后再使用,使用完成之后还需要再存储回外部共享存储区。为了保证任何时刻在进程间都只有一份对象存在,一个进程在获取到对象之后,需要对对象加锁,避免其他进程再将其获取。在进程使用完这个对象之后,需要显式地将对象从内存中删除,并且释放对对象的加锁。

原型模式与享元模式

原型模式和享元模式,前者是在创建多个实例时,对创建过程的性能进行调优;后者是用减少创建实例的方式,来调优系统性能。

原型模式(使用 clone 方法复制一个对象)
1.实现 Cloneable 接口;2.重写 Object 类中的 clone 方法 3 在重写的 clone 方法中调用 super.clone():默认情况下,类不具备复制对象的能力,需要调用 super.clone() 来实现。

深拷贝和浅拷贝
除了基本类型,对于对象的引用以及 List 等类型的成员属性,则只能复制这些对象的引用了。所以简单调用 super.clone() 这种克隆对象方式,就是一种浅拷贝。

深拷贝就是基于浅拷贝来递归实现具体的每个对象

使用场景:如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段都相同)
实际上,创建对象包含的申请内存、给成员变量赋值这一过程,本身并不会花费太多时间,但是,如果对象中的数据需要经过复杂的计算才能得到(比如排序、计算哈希值),或者需要从 RPC、网络、数据库、文件系统等非常慢速的 IO 中读取,这种情况下,我们就可以利用原型模式。

那如何实现深拷贝:
第一种方法:递归拷贝对象、对象的引用对象以及引用对象的引用对象……直到要拷贝的对象只包含基本数据类型数据,没有引用对象为止。
第二种方法:先将对象序列化,然后再反序列化成新的对象。

享元模式

//抽象享元类

interface Flyweight {
    //对外状态对象
    void operation(String name);
    //对内对象
    String getType();
}

//具体享元类

class ConcreteFlyweight implements Flyweight {
    private String type;

    public ConcreteFlyweight(String type) {
        this.type = type;
    }

    @Override
    public void operation(String name) {
        System.out.printf("[类型(内在状态)] - [%s] - [名字(外在状态)] - [%s]\n", type, name);
    }

    @Override
    public String getType() {
        return type;
    }
}

//享元工厂类

class FlyweightFactory {
    private static final Map<String, Flyweight> FLYWEIGHT_MAP = new HashMap<>();//享元池,用来存储享元对象

    public static Flyweight getFlyweight(String type) {
        if (FLYWEIGHT_MAP.containsKey(type)) {//如果在享元池中存在对象,则直接获取
            return FLYWEIGHT_MAP.get(type);
        } else {//在响应池不存在,则新创建对象,并放入到享元池
            ConcreteFlyweight flyweight = new ConcreteFlyweight(type);
            FLYWEIGHT_MAP.put(type, flyweight);
            return flyweight;
        }
    }
}
public class Client {

    public static void main(String[] args) {
        Flyweight fw0 = FlyweightFactory.getFlyweight("a");
        Flyweight fw1 = FlyweightFactory.getFlyweight("b");
        Flyweight fw2 = FlyweightFactory.getFlyweight("a");
        Flyweight fw3 = FlyweightFactory.getFlyweight("b");
        fw1.operation("abc");
        System.out.printf("[结果(对象对比)] - [%s]\n", fw0 == fw2);
        System.out.printf("[结果(内在状态)] - [%s]\n", fw1.getType());
    }
}

如果对象已经存在于享元池中,则不会再创建该对象了,而是共用享元池中内部数据一致的对象。这样就减少了对象的创建,同时也节省了同样内部数据的对象所占用的内存空间。例如 Java 的 String 字符串,在一些字符串常量中,会共享常量池中字符串对象,从而减少重复创建相同值对象,占用内存空间。

生产者消费者模式

生产者与消费者是通过一个中间容器来解决强耦合关系,并以此来实现不同的生产与消费速度,从而达到缓冲的效果。

1.Object 的 wait/notify/notifyAll 实现生产者消费者
基于 Object 的 wait/notify/notifyAll 与对象监视器(Monitor)实现线程间的等待和通知
2.Lock 中 Condition 的 await/signal/signalAll 实现生产者消费者
显示锁 ReentrantLock 或 ReentrantReadWriteLock 都是基于 AQS 实现的,AQS 中存在一个同步队列(CLH 队列),当一个线程没有获取到锁时就会进入到同步队列中进行阻塞,如果被唤醒后获取到锁,则移除同步队列。

生产消费案例

  private LinkedList<String> product = new LinkedList<String>();
  private AtomicInteger inventory = new AtomicInteger(0);//实时库存

  private int maxInventory = 10; // 最大库存

  private Lock consumerLock = new ReentrantLock();// 资源锁
  private Lock productLock = new ReentrantLock();// 资源锁

  private Condition notEmptyCondition = consumerLock.newCondition();// 库存满和空条件
  private Condition notFullCondition = productLock.newCondition();// 库存满和空条件

  /**
   * 新增商品库存
   * @param e
   */
  public void produce(String e) {
    productLock.lock();
    try {
      while (inventory.get() == maxInventory) {
        notFullCondition.await();
      }

      product.add(e);
      
      System.out.println("放入一个商品库存,总库存为:" + inventory.incrementAndGet());
      
      if(inventory.get()<maxInventory) {
        notFullCondition.signalAll();
      }

    } catch (Exception ex) {
      ex.printStackTrace();
    } finally {
      productLock.unlock();
    }
    
    if(inventory.get()>0) {
      try {
        consumerLock.lockInterruptibly();
        notEmptyCondition.signalAll();
      } catch (InterruptedException e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
      }finally {
        consumerLock.unlock();
      }
    }
    
  }

  /**
   * 消费商品
   * @return
   */
  public String consume() {
    String result = null;
    consumerLock.lock();
    try {
      while (inventory.get() == 0) {
        notEmptyCondition.await();
      }

      result = product.removeLast();
      System.out.println("消费一个商品,总库存为:" + inventory.decrementAndGet());
      
      if(inventory.get()>0) {
        notEmptyCondition.signalAll();
      }
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      consumerLock.unlock();
    }
    
    if(inventory.get()<maxInventory) {
      
      try {
        productLock.lockInterruptibly();
        notFullCondition.signalAll();
      } catch (InterruptedException e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
      }finally {
        productLock.unlock();
      }
    }
    return result;
  }
  /**
   * 生产者
   * @author admin
   *
   */
  private class Producer implements Runnable {

    public void run() {
      for (int i = 0; i < 20; i++) {
        produce("商品" + i);
      }
    }
  }

  /**
   * 消费者
   * @author admin
   *
   */
  private class Customer implements Runnable {

    public void run() {
      for (int i = 0; i < 20; i++) {
        consume();
      }
    }
  }

  public static void main(String[] args) {

    LockConditionTest2 lc = new LockConditionTest2();
    new Thread(lc.new Producer()).start();
    new Thread(lc.new Customer()).start();

  }
}

装饰器模式

为对象动态添加装修功能,它是从一个对象的外部来给对象添加功能,所以有非常灵活的扩展性。
抽象类写基础公共功能,继承类在其基础进行扩展。

重构代码:
大型重构指的是对顶层代码设计的重构,包括:系统、模块、代码结构、类与类之间的关系等的重构,重构的手段有:分层、模块化、解耦、抽象可复用组件等等。小型重构指的是对代码细节的重构,主要是针对类、函数、变量等代码级别的重构,比如规范命名、规范注释、消除超大类或函数、提取重复代码等等。

工厂模式

大部分工厂类都是以“Factory”这个单词结尾的,但也不是必须的,比如 Java 中的 DateFormat、Calender。除此之外,工厂类中创建对象的方法一般都是 create 开头,比如代码中的 createParser(),但有的也命名为 getInstance()、createInstance()、newInstance(),有的甚至命名为 valueOf()(比如 Java String 类的 valueOf() 函数)等等。
使用场景:
代码中存在 if-else 分支判断,动态地根据不同的类型创建不同的对象。针对这种情况,我们就考虑使用工厂模式,将这一大坨 if-else 创建对象的代码抽离出来,放到工厂类中。
还有一种情况,尽管我们不需要根据不同的类型创建不同的对象,但是,单个对象本身的创建过程比较复杂,比如要组合其他类对象,做各种初始化操作。在这种情况下,我们也可以考虑使用工厂模式,将对象的创建过程封装到工厂类中。

建造者模式

如果存在下面情况中的任意一种,我们就要考虑使用建造者模式:
我们把类的必填属性放到构造函数中,强制创建对象的时候就设置。如果必填的属性有很多,把这些必填属性都放到构造函数中设置,那构造函数就又会出现参数列表很长的问题。如果我们把必填属性通过 set() 方法设置,那校验这些必填属性是否已经填写的逻辑就无处安放了。如果类的属性之间有一定的依赖关系或者约束条件,我们继续使用构造函数配合 set() 方法的设计思路,那这些依赖关系或约束条件的校验逻辑就无处安放了。如果我们希望创建不可变对象,也就是说,对象在创建好之后,就不能再修改内部的属性值,要实现这个功能,我们就不能在类中暴露 set() 方法。构造函数配合 set() 方法来设置属性值的方式就不适用了。

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

可以把校验逻辑放置到 Builder 类中,先创建建造者,并且通过 set() 方法设置建造者的变量值,然后在使用 build() 方法真正创建对象之前,做集中的校验,校验通过之后才会创建对象。除此之外,我们把 ResourcePoolConfig 的构造函数改为 private 私有权限。这样我们就只能通过建造者来创建 ResourcePoolConfig 类对象。并且,ResourcePoolConfig 没有提供任何 set() 方法,这样我们创建出来的对象就是不可变对象了。

代理模式

  1. 代理模式的原理与实现在不改变原始类(或叫被代理类)的情况下,通过引入代理类来给原始类附加功能。一般情况下,我们让代理类和原始类实现同样的接口。但是,如果原始类并没有定义接口,并且原始类代码并不是我们开发维护的。在这种情况下,我们可以通过让代理类继承原始类的方法来实现代理模式。

  2. 动态代理的原理与实现静态代理需要针对每个类都创建一个代理类,并且每个代理类中的代码都有点像模板式的“重复”代码,增加了维护成本和开发成本。对于静态代理存在的问题,我们可以通过动态代理来解决。我们不事先为每个原始类编写代理类,而是在运行的时候动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。

  3. 代理模式的应用场景代理模式常用在业务系统中开发一些非功能性需求,比如:监控、统计、鉴权、限流、事务、幂等、日志。我们将这些附加功能与业务功能解耦,放到代理类统一处理,让程序员只需要关注业务方面的开发。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值