常用设计模式

185 阅读9分钟

单例模式

/**
饿汉模式 在类加载时,会加载该类,使用了 static 修饰了成员变量 instance,所以该变量会在类初始化的过程中
被收集进类构造器即<clinit>方法中。
在多线程场景下,JVM 会保证只有一个线程能执行该类的<clinit>方法,其它线程将会被阻塞等待。
等到唯一的一次<clinit>方法执行完成,其它线程将不会再执行<clinit>方法,转而执行自己的代码。
也就是说,static 修饰了成员变量 instance,在多线程的情况下能保证只实例化一次.
这种方式实现的单例模式,在类初始化阶段就已经在堆内存中开辟了一块内存,用于存放实例化对象,所以也称为饿汉模式。
 */
public final class Singleton{
    private static Singleton instance = new Singleton();
    private Singleton(){}
    public static Singleton(){
        return instance;
    }
}

/**
懒汉模式 
懒汉模式就是为了避免直接加载类对象时提前创建对象的一种单例设计模式。
该模式使用懒加载方式,只有当系统使用到类对象时,才会将实例加载到堆内存中。
 */
public final class Singleton{
    private static Singleton instance = null;
    private Singleton(){}
    // 由于这里获取的时候没有加锁,所以多线程获取的时候有可能会在堆中创建多个对象,然后覆盖复制给instance
    public static Singleton getInstance(){
        if(null ==instance){
            instance = new Singleton();
        }
        return instance;
    }
}

/**
双重检查锁
这里的双重检查锁就是为了解决上面的获取实例的时候多线程的问题。使用双重检查所主要是为了可以使多条线程进来的时候如果线程a,已经
创建了线程,线程b挂在锁上等待执行,这时候有线程c来了,因为线程a创建完之后释放锁,会刷新内存,这时候线程c在第一个检查null的时候,
就已经可以返回了,不至于等在synchronized代码上,提高性能避免同步锁上挂了过多的线程。

其次这里还是会出现一个问题,就是同步代码块里面的指令重排的问题,因为这里特意加上了一个类成员变量list,实际上在创建Singleton对象
的时候,编译优化有可能会将创建的对象先复制给instance静态变量,但是这个时候list的初始化还没有完成,因为instance是一个静态变量,
是所有线程共享的,这时候有线程进来调用到了第一次判断,发现不为null,直接就返回了根本不会进入同步代码块,因为这时候list还没有初始化,
调用的时候就会报错,为了避免这个问题,其实可以在instance静态变量上用volatile修饰,表示赋值语句不能被指令重拍,
也就是赋值语句之前你怎么指令重拍没关系,但是之前的语句不能排在赋值后面,比如对于
list的初始化就能不能放在赋值后面,用这种方法可以避免读取错误。volatile变量不仅能保证变量的能见性,还避免指令重排。
 */
 public final class Singleton{
     private volatile static Singleton instance = null;
     private List<String> list = null;
     //这里加上了一个成员变量的初始化是为了说明指令重排的问题
     private Singleton(){
         list = new ArrayList();
     }
     public staic Singleton getInstance(){
         if(null == instance){
            synchronized(Singleton.class){
                if(null == instance){
                    instance = new Singleton();
                }
            }
         }
         return instance;
     }

 }


 /**
 内部类模式
 这种模式就是利用了懒汉模式的初始化的时候JVM只允许一个线程进行创建,当加载InnerSingleton的时候,因为static变量的复制操作被封装在了<clinit>方法中,
 初始化阶段就会允许一个线程进行创建new Singleton的操作,当加载Singleton类的时候,没有static的变量,只在getInstance的时候去加载了内部类,
 进行static变量的初始化,所以这样就是实现了懒加载的功能,并且线程安全
  */
public final class Singleton{
    public List List = null;

    private Singleton(){
        list = new ArrList<String>();
    }

    public static class InnerSingleton{
        private static instance = new Singleton();
    }

    public static Singleton getInstance(){
        return InnerSingleton.instance;
    }
}

原型模式

原型模式的思想就是加速对象的创建,比如在日常的业务场景中,经常会遇到for循环组装对象的场景。比如

for(){
    Test test = new Test();
    test.setxxx();
}

其实可以利用Cloneable接口,集成并实现clone()方法,这个方法调用的是本地方法,会把对象直接从内存层面进行拷贝生成新的对象,从而达到加速拷贝的目的。例如

class Stu implement Cloneable{
    private String name;
    private List list;
    @Override
    public Stu clone(){
     Stu stu = null; 
     try { 
     stu = (Stu) super.clone(); 
         
     } catch (CloneNotSupportedException e) { e.printStackTrace(); } 
     return stu;
    }
}

这里的super.clone()其实是向上调用的object.clone(),因为所有的类都是Object的子类,所以新创建对象将属性都复制过去,但是这里的拷贝是一个浅拷贝,比如上面的list,只会把list引用拷贝给新的对象,也就是你clone之后的对象改变list的值,原始对象也会改变,解决这个问题的办法就是神拷贝,其实也就是重新赋值一遍,其实就是对这样的引用对象要递归clone()然后赋值,这个其实挺麻烦的。。。。

在Spring里面也有应用,比如注解@Scope("property")其实就是创建多实例而不是单例。

class Stu implement Cloneable{
    private String name;
    private List list;
    @Override
    public Stu clone(){
     Stu stu = null; 
     try { 
     stu = (Stu) super.clone(); 
     stu.list = list.clone();
     } catch (CloneNotSupportedException e) { e.printStackTrace(); } 
     return stu;
    }
}

享元模式

享元模式的场景就是为了应对对象的复用,简单来讲其实就是用个缓存比如Map把它给存起来,然后再通过一定的key给他拿出来。

其实说的学术一点儿就是享元模式是用了三个角色来做的,一个是抽象享元类(其实就是你要复用的对象的接口),二是具体享元类(其实就是实现接口的服用的对象),三是享元工厂(其实就是里面放了个map或者容器,容器里面有对象就拿出来用,没有就创建放进去下次拿出来用)。其中享元类的内部分了内部数据和外部数据,其实内部数据就是可以复用的数据,外部数据就是根据外部传进来的东西进行运算后返回的东西。通过这样的方式可以节省内部的存储空间。比如String类也有这样的的用法,JVM利用运行时常量池,存储编译期间可以扫描出来的字符串,放到常量池中,这样用的时候都走常量池减少内存占用。(时间上如果不是在代码里直接赋值,而是运行时创建的string比如网络上传过来的,他还是会在堆里直接创建string对象)

举个例子来说。


//抽象享元类
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;
        }
    }
}

装饰器模式

/**
装饰器模式(俄罗斯套娃)
责任链模式、策略模式与装饰器模式有很多相似之处,其实就是为了省略if-else,基于接口或者实现组装的调用链,一层套一层,有点儿像
俄罗斯套娃,下面用一个商品优惠价格的例子来说明一下。这个订单只能购买一个商品,多了懒得写其实也可以一个订单里多个商品,无非就是
多一层循环而已。这样做的好处就是,不管有多少个活动都可以直接加上去,在实际开发中,一般商品的和活动的关联信息会有一张关联表保存,这样可以把这个关联信息放到redis或者本地缓存里,到时候可以直接便利这个一对多的关联关系直接构造装饰器的套娃使用,达到随意优惠搭配的效果。(一般来说会有个优惠优先级或者冲突的的逻辑,这个可以通过缓存时,来进行排序或者去充的优化)
 */

 public class Order{
     String orderId;//订单id
     String orderNo;//订单号
     // 别用dubble float 存价格要用BigDecimal
     BigDecimal payPrice;//总价
     Merchandise merchandise;//商品详情类
 }

 public class Merchandise{
     String sku;
     String name;
     BigDecimal price;
     Map<Integer,Promotions> supportPromotions;//可以用的促销类型 1/优惠券 2/红包
 }

public class Promotions{
    public BigDecimal getMoney(); //得到优惠的钱
}

public class UserCoupon implements Promotions{
    int id;
    int userId;
    String sku;
    BigDecimal coupon;//优惠金额

    public BigDecimal getMoney(){
        return this.coupon;
    }


}
public class UserRedPacket implements Promotions{
    int id;
    int userId;
    String sku;
    BigDecimal redPacket;//红包金额

    public BigDecimal getMoney(){
        return this.redPacket;
    }
}

/**
    计算金额的接口类
 */

 public interface IBaseCount{
     BigDecimal countPayMoney(Order order);
 }

 /**
 金额的基础类主要获取商品原价格 
 */

 public class BaseCount implements IBaseCount{

     IBaseCount iBaseCount;
    public BaseCount(IBaseCount count){
        iBaseCount = count;
    }

     public BigDecimal countPayMoney(Order order){
            return order.getPayMoney();
     }
 }

/**
优惠券计算类
 */
 public class CouponCount{
     public CouponCount(IBaseCount count){
        super(count);
     }
    public BigDecimal countPayMoney(Order order){
        BigDecimal payTotalMoney = super.countPayMoney(order);
        //获取优惠券类型的优惠价格
        BigDecimal coupon = order.getMerchandise().getSupportPromotions(1).getMoney();
        order.setPayTotalMoney(payTotalMoney.substract(coupon));
        return order.getPayTotalMoney();
    }
 }

 /**
红包计算类
 */
 public class RedPacketCount{
     public CouponCount(IBaseCount count){
        super(count);
     }
    public BigDecimal countPayMoney(Order order){
        BigDecimal payTotalMoney = super.countPayMoney(order);
        //获取优惠券类型的优惠价格
        BigDecimal coupon = order.getMerchandise().getSupportPromotions(2).getMoney();
        order.setPayTotalMoney(payTotalMoney.substract(coupon));
        return order.getPayTotalMoney();
    }
 }

 /**
    最后看一下怎么获取到最终订单价格 其实就是循环把计算价格的类一层一层套起来,然后在调用
    接口通用的计算价格的方法,一层一层传递计算出订单的最终价格
  */

  public static void main(String[] args){
      Order order = new Order();
      init(order);
      IBaseCount baseCount = new BaseCount();
      order.getMerchandise().getSupportPromotions((k,v)->{
        if(k==1){//优惠券
            baseCount = new CouponCount(baseCount);
        }else{ //红包
            baseCount = new RedPacketCount(baseCount);
        }
      });
      baseCount.countPayMoney(order);
  }