谈谈设计模式

先从行为形设计模式说,我项目中也有体现

Posted by Zheng Yang on August 8, 2024

设计模式

单例模式

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

懒汉式

类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建。

public class Singleton {
    //私有构造方法
    private Singleton() {}

    //在成员位置创建该类的对象
    private static Singleton instance;

    //对外提供静态方法获取该对象
    public static synchronized Singleton getInstance() {
	//这里增加了一个同步锁,这样如果线程A进入的话,线程B就无法进入,则能保证线程安全。
        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

线程安全

public class Singleton {

    //私有构造方法
    private Singleton() {}

    //在成员位置创建该类的对象
    private static Singleton instance;

    static {
        instance = new Singleton();
    }

    //对外提供静态方法获取该对象
    public static Singleton getInstance() {
        return instance;
    }
}

线程不安全

public class Singleton { 

    //私有构造方法
    private Singleton() {}

    private static Singleton instance;

   //对外提供静态方法获取该对象
    public static Singleton getInstance() {
        //第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
        if(instance == null) {
            synchronized (Singleton.class) {
                //抢到锁之后再次判断是否为null
                if(instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

双重检验锁

我们来解读一下,双重校验锁的意义何在,为什么要这样设计。

首先,第一次校验,也就是第一个判断if(singleton == null),意义是由于单利模式只需创建一个实列,所以当第一次创建实列成功之后,再次调用Singleton.getInstance()就没有必要进入同步锁代码块,直接返回之前创建的实列即可。

第二次校验,也就是第二次判断if(singleton == null),是为了防止二次创建实列,我们假设一种状况,当singleton还未被创建的时候,线程r1 调用了getInstance 方法,由于此时的singleton 为空,则可以进入第一层判断,线程r1正准备继续执行,此时,线程r2抢占cpu资源,此时r2也调用了getInstance 方法,同理线程r1并没有实例化singleton,线程r2也可以进去判断,然后继续往下执行,进入到同步代码块,进入第二层判断,完成了singleton 的创建,并分配空间,r2线程运行周期结束。执行任务又回到了r1,如果没有第二层判断,线程r1 也会创建一个实列(r2线程已经创建一个实列,第二层判断为false),这样就完全避免掉多线程环境下会创建多个实列的的问题。

饿汉式

public class Singleton {
    //私有构造方法
    private Singleton() {}

    //在成员位置创建该类的对象
    private static Singleton instance = new Singleton();

    //对外提供静态方法获取该对象
    public static Singleton getInstance() {
        return instance;
    }
}

该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象instance。instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。

枚举类

枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。

/**
 * 枚举方式
 */
public enum Singleton {
    INSTANCE;
}

责任链模式

image-20240808100015604.png

责任链模式(Chain of Responsibility Pattern)是一种行为设计模式,它允许多个对象处理同一个请求,从而避免请求的发送者和接收者之间的耦合关系。这种模式为请求的处理提供了一条清晰的链,每个对象可以决定是否处理该请求或者将请求转发给链中的下一个对象。

==举例==

image-20240808100807229.png

  1. 下单对象
   public class OrderContext {
       private String seqId;
       private String userId;
       private Long skuId;
       private Integer amount;
       private String userAddressId;
       // Getters and setters
   }
   
  1. 创建处理者接口

    public interface OrderHandleIntercept {
        int sort();
        OrderContext handle(OrderContext context);
    }
       
    
  2. 创建具体处理者类

    @Component
    public class RepeatOrderHandleInterceptService implements OrderHandleIntercept {
        @Override
        public int sort() {
            return 1; // 执行顺序为 1
        }
       
        @Override
        public OrderContext handle(OrderContext context) {
            System.out.println("通过seqId,检查客户是否重复下单");
            return context;
        }
    }
       
    @Component
    public class ValidOrderHandleInterceptService implements OrderHandleIntercept {
        @Override
        public int sort() {
            return 2; // 执行顺序为 2
        }
       
        @Override
        public OrderContext handle(OrderContext context) {
            System.out.println("检查请求参数是否合法,并且获取客户的银行账户");
            return context;
        }
    }
       
    @Component
    public class BankOrderHandleInterceptService implements OrderHandleIntercept {
        @Override
        public int sort() {
            return 3; // 执行顺序为 3
        }
       
        @Override
        public OrderContext handle(OrderContext context) {
            System.out.println("检查银行账户是否合法,调用银行系统检查银行账户余额是否满足下单金额");
            return context;
        }
    }
    
  3. 处理器链类

    @Component
    public class OrderHandleChainService implements ApplicationContextAware {
        private List<OrderHandleIntercept> handleList = new ArrayList<>();
       
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            Map<String, OrderHandleIntercept> serviceMap = applicationContext.getBeansOfType(OrderHandleIntercept.class);
            handleList = serviceMap.values().stream()
                    .sorted(Comparator.comparing(OrderHandleIntercept::sort))
                    .collect(Collectors.toList());
        }
       
        public OrderContext execute(OrderContext context) {
            for (OrderHandleIntercept handleIntercept : handleList) {
                context = handleIntercept.handle(context);
            }
            return context;
        }
    }
       
    
  4. 进行单元测试

    @Autowired
    private OrderHandleChainService orderHandleChainService;
       
    @Test
    public void test02() {
        orderHandleChainService.execute(new OrderContext());
    }
       
    
  5. 执行结果

    通过seqId,检查客户是否重复下单
    检查请求参数是否合法,并且获取客户的银行账户
    检查银行账户是否合法调用银行系统检查银行账户余额是否满足下单金额
       
    

策略模式

image-20240808182945137.png 策略模式有三个组成角色: 抽象策略(Strategy)类 具体策略(Concrete Strategy)类 环境(Context)类

public class StrategyPattern {
    public static void main(String[] args) {
        Context c = new Context();
        Strategy s = new ConcreteStrategyA();
        c.setStrategy(s);
        c.strategyMethod();
        System.out.println("-----------------");
        s = new ConcreteStrategyB();
        c.setStrategy(s);
        c.strategyMethod();
    }
}
//抽象策略类
interface Strategy {
    public void strategyMethod();    //策略方法
}
//具体策略类A
class ConcreteStrategyA implements Strategy {
    public void strategyMethod() {
        System.out.println("具体策略A的策略方法被访问!");
    }
}
//具体策略类B
class ConcreteStrategyB implements Strategy {
    public void strategyMethod() {
        System.out.println("具体策略B的策略方法被访问!");
    }
}
//环境类
class Context {
    private Strategy strategy;
    public Strategy getStrategy() {
        return strategy;
    }
    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }
    public void strategyMethod() {
        strategy.strategyMethod();
    }
}

是这种样式

Context c = new Context();
    if(conditions){
        // 逻辑1
        Strategy s = new ConcreteStrategyA();
        c.setStrategy(s);
        c.strategyMethod();
    } else {
        // 逻辑2
        Strategy s = new ConcreteStrategyB();
        c.setStrategy(s);
        c.strategyMethod();
    }
}

小结一下,即使用了策略模式,你该写的业务逻辑照常写,到逻辑分派的时候,还是变相的if else。而它的优化点是抽象了出了接口,将业务逻辑封装成一个一个的实现类,任意地替换。在复杂场景(业务逻辑较多)时比直接 if else 来的好维护些。

大家仔细想想,针对上述写法其实有2个痛点 1.具体策略类会过多 2.还无法彻底消除if else

第一个问题我们其实可以这样解决,把抽象策略和具体策略放在一个枚举类里

public enum Strategy {
    A{

        @Override
        public void exe() {
            System.out.println("执行具体策略A");
        }

    },
    B{
        @Override
        public  void exe() {
            System.out.println("执行具体策略B");
        }

    };

    public abstract void exe();

}

再来看第二个痛点。彻底消除if else。 对了!直接用Map不就行了吗,Map<条件,具体策略> 预先put进去条件,需要的时候get不就行了吗

所以,解决之道就是 枚举类+Map 完整代码如下

public enum Strategy {
    A{

        @Override
        public void exe() {
            System.out.println("执行具体策略A");
        }

    },
    B{
        @Override
        public  void exe() {
            System.out.println("执行具体策略B");
        }

    };

    public abstract void exe();

}
public class Test {
    public static void main(String[] args) {
        Map<String, Strategy> map=new LinkedHashMap<>();
        map.put("A",Strategy.A);
        map.put("B",Strategy.B);

        String str="A";
        map.get(str).exe();
    }

}


总结 写代码时总会出很多的if…else,或者case。如果在一个条件语句中又包含了多个条件语句就会使得代码变得臃肿,维护的成本也会加大,而策略模式就能较好的解决这个问题。

先介绍了下策略模式,讲明了应用场景和优缺点,引出了策略模式的三大角色: 抽象策略;具体策略;环境。 然后讲解了下策略模式的应用,普通写法还无法完全消除if else。 优化→枚举类+Map。 来解决策略模式,具体策略类过多和无法完全消除if else的痛点。

模板模式

image-20240809100439463.png

对原理类图的说明:

AbstractClass 抽象类, 类中实现了模板方法(template),定义了算法的骨架,具体子类需要去实现 其它的抽象方法 operation2,3,4 ConcreteClass 实现抽象方法,假设是operation2,3,4, 以完成算法中特定子类的具体业务步骤

==举例==

编写制作豆浆的程序,说明如下:

  • 制作豆浆的流程:选材—>添加配料—>浸泡—>放到豆浆机打碎
  • 通过添加不同的配料,可以制作出不同口味的豆浆
  • 选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的、

image-20240809100942219.png

// 抽象类,表示豆浆	SoyaMilk.java
public abstract class SoyaMilk {
	// 模板方法:可以做成final,不让子类去覆盖
	final void make() {
		select();
		addCondiment();
		soak();
		beat();
	}
	
	//选材料
	void select() { System.out.println("第一步:选择新鲜的豆子"); }
	//添加不同的配料:抽象方法,由子类具体实现
	abstract void addCondiment();
	//浸泡
	void soak() { System.out.println("第三步:豆子和配料开始浸泡3H"); }
	//榨汁
	void beat() { System.out.println("第四步:豆子和配料放入豆浆机榨汁"); }
}

// RedBeanSoyaMilk.java
public class ReadBeanSoyaMilk extends SoyaMilk {
	@Override
	void addCondiment() {
		System.out.println("第二步:加入上好的红豆");
	}
}

// PeanutSoyMilk.java
public class PeanutSoyaMilk extends SoyaMilk {
	@Override
	void addCondiment() {
		System.out.println("第二步:加入上好的花生");
	}
}

// Client.java
public class Client {
	public static void main(String[] args) {
		System.out.println("=======制作红豆豆浆=======");
		SoyaMilk redBeanSoyaMilk = new ReadBeanSoyaMilk();
		redBeanSoyaMilk.make();
		
		System.out.println("=======制作花生豆浆=======");
		SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
		peanutSoyaMilk.make();
	}
}

基本思想是:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改.

好处:

实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用; 既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现。 该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大