Bootstrap

大白话讲解设计模式

工作接近半年了,从高中接触编程到大学到现在工作依旧热情不减,最近在阅读Spring源码的时候,我认识到他的代码写的是真的优雅。对比自己的每次都感觉自己代码写的很烂可能是写业务写多了?打算在博客记录自己的阅读记录。这篇设计模式就是阅读源码的前期,可以先了解,在阅读Spring的时候逐渐学会他们的思想而不是单独成为一个只会CRUD的码农。等阅读完源码之后再更新Spring源码相关文章。

设计模式六大原则

1、开闭原则(Open Close Principle)

开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。

2、里氏代换原则(Liskov Substitution Principle)

里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

3、依赖倒转原则(Dependence Inversion Principle)

这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。

4、接口隔离原则(Interface Segregation Principle)

这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。

5、迪米特法则,又称最少知道原则(Demeter Principle)

最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。

6、合成复用原则(Composite Reuse Principle)

合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。

设计模式的分类

创建型模式

工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式

适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式

行为模式

策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

策略模式

什么是策略模式

策略模式是对算法的包装,是把使用算法的责任和算法本身分割开来,委派给不同的对象管理,最终可以实现解决多重if判断问题。

1.环境(Context)角色:持有一个Strategy的引用。

2.抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。

3.具体策略(ConcreteStrategy)角色:包装了相关的算法或行为。

定义策略接口->实现不同的策略类->利用多态或其他方式调用策略

为什么叫做策略模式

每个if判断都可以理解为就是一个策略

策略模式优缺点

优点

  1. 算法可以自由切换(高层屏蔽算法,角色自由切换)
  2. 避免使用多重条件判断(如果算法过多就会出现很多种相同的判断,很难维护)
  3. 扩展性好(可自由添加取消算法 而不影响整个功能)

缺点

  1. 策略类数量增多(每一个策略类复用性很小,如果需要增加算法,就只能新增类)
  2. 所有的策略类都需要对外暴露(使用的人必须了解使用策略,这个就需要其它模式来补充,比如工厂模式、代理模式)

使用方式(案例)

写一个支付聚合平台

在这里插入图片描述

  1. 使用工厂模式初始化具体 策略class
  2. 将所有具体实现的策略存放在map集合中,key为类名小写 value具体的class地址(枚举存放)
  3. key:ali_pay value:com.xxxxxAIipLAliPayStrategy
  4. 这样写的话,再我们再添加新策略的适合不用写if了,只需要写枚举和实现就可以了

优化下面的代码–通过策略模式解决多重if判断问题

public  String toPayHtml2(String payCode){
    if(payCode.equals("ali_pay")){
        return  "调用支付宝接口...";
    }
    if(payCode.equals("xiaomi_pay")){
        return  "调用小米支付接口";
    }
    if(payCode.equals("yinlian_pay")){
        return  "调用银联支付接口...";
    }
    return  "未找到该接口...";
}
1、先写共同方法的行为 PayStrategy(抽象角色)
public interface PayStrategy {

    /**
     * 共同算法实现骨架
     * @return
     */
     public String toPayHtml();
}
2、创建具体策略的实现 ConcreteStrategy (具体实现角色)
@Component
public class AliPayStrategy  implements PayStrategy {
    public String toPayHtml() {
        return "调用支付宝支付接口";
    }
}
@Component
public class XiaoMiPayStrategy implements PayStrategy {
    public String toPayHtml() {
        return "调用小米支付接口";
    }
}
3、写上下文Context 通过上下文获取具体的策略 --PayContextService (上下文)
3.1、获取的时候肯定需要去容器里获取-----容器是通过枚举作为key的
3.1.1 枚举类 通过枚举类拿到他的class 路径
/*
*策略枚举类 存放所有策略的实现
*/
public enum PayEnumStrategy {
    /**
     *  支付宝支付
     */
    ALI_PAY("com.mayikt.strategy.impl.AliPayStrategy"),
    /**
     *  小米支付
     */
    XiaoMiPay_PAY("com.mayikt.strategy.impl.XiaoMiPayStrategy");
    PayEnumStrategy(String className) {
        this.setClassName(className);
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }
    /**
     * class完整地址
     */
    private String className;

}

3.1.2 通过枚举拿到路径了肯定就 需要工厂帮助我们创建类了 – StrategyFactory–当我们需要简单策略的话直接找工厂就行了
//使用策略工厂获取具体实现
public class StrategyFactory {
    public static PayStrategy getPayStrategy(String strategyType) {
        try {
            // 1.获取枚举中className
            String className = PayEnumStrategy.valueOf(strategyType).getClassName();
            // 2.使用java反射技术初始化类
            return (PayStrategy) Class.forName(className).newInstance();
        } catch (Exception e) {
            return null;
        }
    }
}
4、通过Context上下文策略模式调用

public class PayContextStrategy {
   
   	/*
   		获取具体策略实现
   		获取上下文肯定要有一个容器将他们装起来
   	*/
    public  String toPayHtml(String payCode){
        // 1.验证参数
        if(StringUtils.isEmpty(payCode)){
            return  "payCode不能为空!";
        }
        //使用策略工厂获取具体的策略实现
        PayStrategy payStrategy  = StrategyFactory.getPayStrategy(payCode);
        if(payStrategy == null) return "没有找到具体策略的...";   
        //工厂负责对象的初始化
        return payStrategy;
    }
}

5、Controller通过Context 调用
@RestController
public class PayContextService {

    @Autowired
    private PayContextStrategy PayContextStrategy;
   
    @RequestMapping("/toPayHtml")
    public  String toPayHtml(String payCode){
        return  PayContextStrategy.toPayHtml();
    }
}

责任链模式

什么是责任链模式

客户端发出一个请求,链上的对象都有机会来处理这一请求,而客户端不需要知道谁是具体的处理对象。这样就实现了请求者和接受者之间的解耦,并且在客户端可以实现动态的组合职责链。使编程更有灵活性。

定义:使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。其过程实际上是一个递归调用。

要点主要是:

  1. 有多个对象共同对一个任务进行处理。
  2. 这些对象使用链式存储结构,形成一个链,每个对象知道自己的下一个对象。
  3. 一个对象对任务进行处理,可以添加一些操作后将对象传递个下一个任务。也可以在此对象上结束任务的处理,并结束任务。
  4. 客户端负责组装链式结构,但是客户端不需要关心最终是谁来处理了任务。

多个对象指的是什么意思?

责任链类结构图

  1. 抽象处理者(Handler)角色:定义出一个处理请求的接口。如果需要,接口可以定义 出一个方法以设定和返回对下家的引用。这个角色通常由一个Java抽象类或者Java接口实现。上图中Handler类的聚合关系给出了具体子类对下家的引用,抽象方法handleRequest()规范了子类处理请求的操作。
  2. 具体处理者(ConcreteHandler)角色:具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下家。由于具体处理者持有对下家的引用,因此,如果需要,具体处理者可以访问下家

责任链模式优缺点

优点:

  • 职责链模式的最主要功能就是:动态组合,请求者和接受者解耦。
  • 请求者和接受者松散耦合:请求者不需要知道接受者,也不需要知道如何处理。每个职责对象只负责自己的职责范围,其他的交给后继者。各个组件间完全解耦。
  • 动态组合职责:职责链模式会把功能分散到单独的职责对象中,然后在使用时动态的组合形成链,从而可以灵活的分配职责对象,也可以灵活的添加改变对象职责。

缺点:

  • 产生很多细粒度的对象:因为功能处理都分散到了单独的职责对象中,每个对象功能单一,要把整个流程处理完,需要很多的职责对象,会产生大量的细粒度职责对象。
  • 不一定能处理:每个职责对象都只负责自己的部分,这样就可以出现某个请求,即使把整个链走完,都没有职责对象处理它。这就需要提供默认处理,并且注意构造链的有效性。

责任链模式应用场景

  1. 多条件流程判断 权限控制
  2. ERP系统 流程审批 总经理、人事经理、项目经理
  3. Java过滤器的底层实现Filter
  • 比如:在Java过滤器中客户端发送请求到服务器端,过滤会经过参数过滤、session过滤、表单过滤、隐藏过滤、检测请求头过滤

使用案例(网关权限控制责任链模式)

在网关作为微服务程序的入口,拦截客户端所有的请求实现权限控制 ,比如先判断Api接口限流、黑名单、用户会话、参数过滤。

Api接口限流→黑名单拦截→用户会话→参数过滤

责任链设计模式是怎么保证执行顺序的?

采用链表数据结构的方式,所以只需要获取到第一个handler 所有整个链都会执行

1、先创建handler 抽象类 来定义共同的处理行为
public abstract class GatewayHandler {
    protected GatewayHandler nextGatewayHandler;

    /**
     * 处理业务逻辑
     * 使用抽象类定义方法的共同行为
     * @return true 表示继续执行 false表示不继续执行.. 
     */
    public abstract void service();

    public void setHandler(GatewayHandler gatewayHandler) {
        this.nextGatewayHandler = gatewayHandler;
    }
    protected void nextService(){
         if(nextGatewayHandler!=null){
             nextGatewayHandler.service();;
         }
    }
}
2、创建后续关卡(接口限流、黑名单拦截、会话信息拦截)
@Component
public class CurrentLimitHandler extends GatewayHandler {
    @Override
    public void service() {
        System.out.println("第一关网关限流判断....");
        nextService();
    }
}
@Component
public class BlacklistHandler extends GatewayHandler {
    @Override
    public void service() {
        System.out.println("第二关黑名单拦截判断....");
        nextService();
    }
}
@Component
public class ConversationHandler extends GatewayHandler {
    @Override
    public void service() {
        System.out.println("第三关用户会话拦截判断....");
        nextService();
    }
}
3、创建工厂类用来执行
public class FactoryHandler {

    public static GatewayHandler getFirstGatewayHandler() {
        GatewayHandler gatewayHandler1 = new CurrentLimitHandler();
        GatewayHandler gatewayHandler2 = new BlacklistHandler();
        gatewayHandler1.setNextGatewayHandler(gatewayHandler2);
        GatewayHandler gatewayHandler3 = new ConversationHandler();
        gatewayHandler2.setNextGatewayHandler(gatewayHandler3);
        return gatewayHandler1;
    }
}
4、component 入口
@RestComponent
public class HandlerController{
	
    @RequestMapping("/")
    public String clientHandler(){
        CurrentLimitHandler firstGetewayHandler = FactoryHandler.getFirstGatewayHandler()
            return "success"
    }
}

工厂模式

什么是工厂模式

实现了创建者和调用者分离,工厂模式可以分为简单工厂、工厂方法、抽象工厂、静态工厂模式

工厂模式的作用

  1. 工厂模式是为了解耦:把对象的创建和使用的过程分开。就是Class A 想调用Class B,那么只是调用B的方法,而至于B的实例化,就交给工厂类。
  2. 工厂模式可以降低代码重复。如果创建B过程都很复杂,需要一定的代码量,而且很多地方都要用到,那么就会有很多的重复代码。可以把这些创建对象B的代码放到工厂里统一管理。既减少了重复代码,也方便以后对B的维护。
  3. 工厂模式可以减少错误,因为工厂管理了对象的创建逻辑,使用者不需要知道具体的创建过程,只管使用即可,减少了使用者因为创建逻辑导致的错误。

工厂模式优缺点

优点:

工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式。利用工厂模式可以降低程序的耦合性,为后期的维护修改提供了很大的便利。将选择实现类、创建对象统一管理和控制。从而将调用者跟我们的实现类解耦。工厂与容器概念

代码结构简单。
获取产品的过程更加简单。
满足了开闭原则,即对拓展开放,对修改关闭。

缺点:

拓展较繁琐,要拓展时,需同时改动抽象工厂和工厂实现类

简单工厂模式

简单工厂模式相当于是一个工厂中有各种产品,创建在一个类中,客户无需知道具体产品的名称,只需要知道产品类所对应的参数即可。但是工厂的职责过重,而且当类型过多时不利于系统的扩展维护。

职责过重----比如去汽车店买汽车,我们只需要高速售卖员我们想要什么,它就会领着我们过去。但是如果我们的汽车店很大的话,我们需要看各种牌子的汽车,售卖员就需要去每个品牌下的区域去找对应的汽车。找的会很慢

//汽车厂(4s店)
public interface Car {
	public void run();
}

public class AoDi implements Car {
	public void run() {
		System.out.println("我是奥迪汽车..");
	}
}

public class JiLi implements Car {

	public void run() {
		System.out.println("我是吉利汽车...");
	}
}

public class CarFactory {

	 public static Car createCar(String name) {
		if (StringUtils.isEmpty(name)) {
             return null;
		}
		if(name.equals("奥迪")){
			return new AoDi();
		}
		if(name.equals("吉利")){
			return new JiLi();
		}
		return null;
	}
}
public class Client01 {

	public static void main(String[] args) {
		Car aodi  =CarFactory.createCar("奥迪");
		Car jili  =CarFactory.createCar("吉利");
		aodi.run();
		jili.run();
	}

}

单工厂的优点/缺点

优点:简单工厂模式能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象。明确区分了各自的职责和权力,有利于整个软件体系结构的优化。

缺点:很明显工厂类集中了所有实例的创建逻辑,容易违反GRASPR的高内聚的责任分配原则

工厂方法模式

什么是工厂方法模式

工厂方法模式Factory Method,又称多态性工厂模式。在工厂方法模式中,核心的工厂类不再负责所有的产品的创建,而是将具体创建的工作交给子类去做。该核心类成为一个抽象工厂角色,仅负责给出具体工厂子类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。

@Component
public interface PaymentService {

    public void pay();
}

public class AliPaySercice implements PaymentService {
    @Override
    public void pay() {
        System.out.println("阿里支付对象...");
    }
}

public class WeChatService implements PaymentService {
    @Override
    public void pay() {
        System.out.println("微信支付对象...");
    }
}

class PayMentFactory {

    public static PaymentService getPaymentService(String payType) {
        PaymentService paymentService = null;
        switch (payType) {
            case "ali_pay":
                paymentService = new AliPaySercice();
                break;
            case "wechat_pay":
                paymentService = new WeChatService();
                break;
        }
        return paymentService;
    }
}


抽象工厂模式

什么是抽象工厂模式

抽象工厂简单地说是工厂的工厂,抽象工厂可以创建具体工厂,由具体工厂来产生具体产品。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yPNeVgRB-1679558936887)(assets/image-20220903220221168.png)]

//发动机
public interface Engine {
	void run();
	void start();
}

class EngineA implements Engine {

	public void run() {
      System.out.println("转的快!");
	}

	public void start() {
		 System.out.println("启动快,自动档");
	}

}

class EngineB implements Engine {

	public void run() {
      System.out.println("转的慢!");
	}

	public void start() {
		 System.out.println("启动快,手动档");
	}

}

//座椅
public interface Chair {
	   void run();
}

 class ChairA implements Chair{

	public void run() {
		System.out.println("可以自动加热!");
	}
	
}
 class ChairB implements Chair{

	public void run() {
		System.out.println("不能加热!");
	}
	
}
public interface CarFactory {
	// 创建发动机
	Engine createEngine();
	// 创建座椅
	Chair createChair();
}
public class JiLiFactory implements CarFactory  {

	public Engine createEngine() {
	
		return new EngineA();
	}

	public Chair createChair() {
		
		return new ChairA();
	}

}
public class Client002 {

	 public static void main(String[] args) {
		CarFactory carFactory=new JiLiFactory();
		Engine engine=carFactory.createEngine();
		engine.run();
		engine.start();

	}
	
}

简单工厂、工厂方法、抽象工厂之小结、区别

  1. 简单工厂 : 用来生产同一等级结构中的任意产品。(不支持拓展增加产品)
  2. **工厂方法 :**用来生产同一等级结构中的固定产品。(支持拓展增加产品)
  3. **抽象工厂 :**用来生产不同产品族的全部产品。(不支持拓展增加产品;支持增加产品族)

模板方法设计模式

什么是模版方法

  • 定义了一个操作中的算法的骨架,而将部分步骤的实现在子类中完成。

    模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

  • 模板方法模式是所有模式中最为常见的几个模式之一,是基于继承的代码复用的基本技术,没有关联关系。 因此,在模板方法模式的类结构图中,只有继承关系。

核心设计要点:

AbstractClass : 抽象类,定义并实现一个模板方法。这个模板方法定义了算法的骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类去实现
ConcreteClass : 实现实现父类所定义的一个或多个抽象方法。

模版方法应用场景

  1. 比如聚合支付平台中系统回调代码重构
  2. Servlet 请求

模版方法抽象类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LcLrUrE1-1679558936888)(assets/image-20220903222350568.png)]

我们这个支付平台需要,拿到第三方平台的回调数据。

  1. 暴露一个接口,提供给第三方支付回调通知
  2. 多家支付平台回调通知参数的报文都不同(但是通知的行为是相同的)

参数不同、行为相同------>都是解析回调通知报文,再修改支付状态为已成功

异步回调流程

  1. 解析报文(验证签名)(不同 — 定义好方法交给子类去实现)
  2. 日志收集(相同)
  3. 如果解析报文成功的话,修改支付状态为已成功(前半部分相同) 返回不同的支付结果–返回值不同(每个平台不同)

模板方法设计模式

提取定义好整体的骨架,不同的行为让子类实现,相同的行为直接定义在抽象类重复用

相同的行为就定再抽象方案重,不同行为的实现定义在子类去实现

1、将抽象类的骨架定义好 – (模版方法抽象类)

@Slf4j
@Component
public abstract class AbstractPayCallbackTemplate {

    /**
     * 异步回调业务
     *
     * @return
     */
    public String asyncCallBack() {
        // 1. 支付回调验证参数
        Map<String, String> verifySignatureMap = verifySignature();
        // 2. 参数验证成功,写入日志中..
        payLog(verifySignatureMap);
        String analysisCode = verifySignatureMap.get("analysisCode");
        if (!analysisCode.equals("200")) {
            return resultFail();
        }
        // 3. 执行回调异步相关逻辑
        return asyncService(verifySignatureMap);

    }


    /**
     * 支付回调验证参数
     *
     * @return
     */
    protected abstract Map<String, String> verifySignature();

    /**
     * 使用多线程异步写入日志
     *
     * @param verifySignatureMap
     */
    @Async
    private void payLog(Map<String, String> verifySignatureMap) {
        log.info(">>>>>>>>>>第二步 写入payLog........");
    }

    /**
     * 每个子类需要实现 实现业务解析操作
     *
     * @return
     */
    protected abstract String asyncService(Map<String, String> verifySignatureMap);

    /**
     * 异步返回结果..
     *
     * @return
     */
    protected abstract String resultSuccess();

    /**
     * 异步返回失败
     *
     * @return
     */
    protected abstract String resultFail();
}

2、具体实现模板

@Component //注入到springBoot 容器中去
@Slf4j
public class AliPayCallbackTemplate extends AbstractPayCallbackTemplate {
    @Override
    protected Map<String, String> verifySignature() {
        //>>>>假设一下为银联回调报文>>>>>>>>>>>>>>>>
        log.info(">>>>>第一步 解析支付宝据报文.....verifySignature()");
        Map<String, String> verifySignature = new HashMap<>();
        verifySignature.put("price", "1399");
        verifySignature.put("orderDes", "充值蚂蚁课堂永久会员");
        // 支付状态为1表示为成功....
        verifySignature.put("aliPayMentStatus", "1");
        verifySignature.put("aliPayOrderNumber", "201910101011");
        // 解析报文是否成功 200 为成功..
        verifySignature.put("analysisCode", "200");
        return verifySignature;
    }

    @Override
    protected String asyncService(Map<String, String> verifySignatureMap) {
        log.info(">>>>>第三步asyncService()verifySignatureMap:{}", verifySignatureMap);
        String paymentStatus = verifySignatureMap.get("aliPayMentStatus");
        if (paymentStatus.equals("1")) {
            String aliPayOrderNumber = verifySignatureMap.get("aliPayOrderNumber");
            log.info(">>>>orderNumber:{aliPayOrderNumber},已经支付成功 修改订单状态为已经支付...");
        }
        return resultSuccess();
    }

    @Override
    protected String resultSuccess() {
        return "ok";
    }
}

工厂模式获取模板

public class TemplateFactory {
	// 去springBoot容器中去拿
    public static AbstractPayCallbackTemplate getPayCallbackTemplate(String templateId) {
        AbstractPayCallbackTemplate payCallbackTemplate = (AbstractPayCallbackTemplate) SpringUtils.getBean(templateId);
        return payCallbackTemplate;
    }
}

SpringUtils

@Component
public class SpringUtils implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    //获取applicationContext
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    //通过name获取 Bean.
    public static Object getBean(String name){
        return getApplicationContext().getBean(name);
    }

    //通过class获取Bean.
    public static <T> T getBean(Class<T> clazz){
        return getApplicationContext().getBean(clazz);
    }

    //通过name,以及Clazz返回指定的Bean
    public static <T> T getBean(String name,Class<T> clazz){
        return getApplicationContext().getBean(name, clazz);
    }

}

模式模式优缺点

  • 优点
    • 模板方法模式通过把不变的行为搬移到超类,去除了子类中的重复代码。子类实现算法的某些细节,有助于算法的扩展。通过一个父类调用子类实现的操作,通过子类扩展增加新的行为,符合“开放-封闭原则”。
  • 缺点
    • 每个不同的实现都需要定义一个子类,这会导致类的个数的增加,设计更加抽象。
  • 适用场景
    • 在某些类的算法中,用了相同的方法,造成代码的重复。控制子类扩展,子类必须遵守算法规则。

策略模式与模版方法模式的区别?

策略模式是针对不同的骨架,不同的方法-----主要针对 多个if的时候每个if的小的一个行为

模板方法模式是要提取定义好相同骨架-----解决 整体骨架有相同的行为放在抽象类中实现,不同的行为放在子类中进行实现

装饰者模式

什么是装饰者模式

在不改变原有对象的基础上附加功能,相比生成子类更灵活。

装饰者模式应用场景

动态的给一个对象添加或者撤销功能。

装饰者模式优缺点

优点:可以不改变原有对象的情况下动态扩展功能,可以使扩展的多个功能按想要的顺序执行,以实现不同效果。
**缺点:**更多的类,使程序复杂

装饰者模式类图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ru6Zkzfl-1679558936889)(assets/image-20220903232035217.png)]

装饰者模式定义

  1. 抽象组件:定义一个抽象接口,来规范准备附加功能的类

  2. 具体组件:将要被附加功能的类,实现抽象构件角色接口

  3. 抽象装饰者:持有对具体构件角色的引用并定义与抽象构件角色一致的接口

  4. 具体装饰:实现抽象装饰者角色,负责对具体构件添加额外功能。

    • protected ComponentGateway componentGateway;

    • setComponentGateway(ComponentGateway componentGateway)

装饰者模式应用场景

使用网关实现API接口安全控制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iOjI4mzm-1679558936889)(assets/image-20220904000958001.png)]

  1. 获取基本网关参数… HttpServletRequest

  2. 下面继续实现的操作 后面可能会新增。------ 刚刚想做,但是第一次后面的功能可能想不到—打地基

    1. 网关中新增日志收集

    2. 网关中新增API接口限流

    3. 网关中新增验证用户Token

    4. 实现黑名单拦截

1、定义一个抽象的接口

//定义 构建抽象的 附件功能类
public abstract class GatewayComponent {
    public abstract void service();
}

2、定义被装饰角色

public class BasicComponentGateway extends GatewayComponent {
    public void service() {
        System.out.println("第一步 >>> 网关中获取基本的操作...实现");
    }
}

3、定义抽象装饰角色

public abstract class AbstractDecorator extends GatewayComponent {
    protected GatewayComponent componentGateway;

    public AbstractDecorator() {

    }

    public AbstractDecorator(GatewayComponent componentGateway) {
        this.componentGateway = componentGateway;
    }

    public void service() {
        componentGateway.service();
    }

    public void setComponentGateway(GatewayComponent componentGateway) {
        if (componentGateway != null)
            this.componentGateway = componentGateway;
    }
}

4、定义具体装饰角色

public class LogDecorator extends AbstractDecorator {
    public LogDecorator() {

    }

    public LogDecorator(GatewayComponent componentGateway) {
        super(componentGateway);
    }

    @Override
    public void service() {
        // 调用装饰类service
        super.service();
        // 日志收集
        System.out.println("第二步>>>>日志的采集.....");
    }
}
public class LimitDecorator extends AbstractDecorator {
    public LimitDecorator() {

    }
    public LimitDecorator(GatewayComponent componentGateway) {
        super(componentGateway);
    }

    @Override
    public void service() {
        // 1.传递日志收集装饰类
        System.out.println(super.getClass().toString());
        super.service();
        System.out.println("第三步>>>>API接口限流");
    }
}

5、使用工厂获取装饰类

public class FactoryGateway {
    
    @Autowired
    private DecoratorMapper decoratorMapper;

    public GatewayComponent getComponentGateway() {
        LimitDecorator limitDecorator = new LimitDecorator();
        LogDecorator logDecorator = new LogDecorator();
        limitDecorator.setComponentGateway(logDecorator);
        logDecorator.setComponentGateway(new BasicComponentGateway());
        return limitDecorator;
    }
}

另外一种方式

//ComponentGateway componentGateway = new LimitDecorator(new LogDecorator(new BasicComponentGateway()));
//componentGateway.service();


public class FactoryGateway {

    public static GatewayComponent getGatewayComponent() {
        // 1.新增日志收集
        LogDecorator LogDecorator = new LogDecorator();
        // 2.新增Api接口限流
        LimitDecorator limitDecorator = new LimitDecorator();
        limitDecorator.setGatewayComponent(LogDecorator);
        // 3.创建基本网关获取参数功能
        BasicComponentGateway basicComponentGateway = new BasicComponentGateway();
        LogDecorator.setGatewayComponent(basicComponentGateway);
        return limitDecorator;
    }

    public static void main(String[] args) {
        GatewayComponent gatewayComponent = FactoryGateway.getGatewayComponent();
        gatewayComponent.service();
    }
}

代理模式

什么是代理模式

为其他对象提供一种代理以控制对这个对象的访问。

为什么使用代理模式

中介隔离:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。

不是跟你说了都不知道类名字!!!!
这个根本原因就是不说嘞
说不到点子上,通过反射!!!!

开闭原则,增加功能代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。

代理模式实现原理

代理模式主要包含三个角色,即抽象主题角色(Subject)、委托类角色(被代理角色,Proxied)以及代理类角色(Proxy),如上图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wK7CYcWM-1679558936890)(assets/wps3.jpg)]

抽象主题角色:可以是接口,也可以是抽象类;

委托类角色:真实主题角色,业务逻辑的具体执行者;

代理类角色:内部含有对真实对象RealSubject的引用,负责对真实主题角色的调用,并在真实主题角色处理前后做预处理和后处理。

代理模式应用场景

SpringAop、日志收集、权限控制、过滤器、RPC远程调用

代理模式创建的方式

静态代理和动态代理

静态代理

静态代理是由程序员创建或工具生成代理类的源码,再编译代理类。

所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

一句话,自己手写代理类就是静态代理。

基于接口实现方式

public interface OrderService {
    void order();
}

public class OrderServiceImpl implements OrderService {
    public void order() {
        System.out.println("用户下单操作..");
    }
}

public class OrderServiceProxy implements OrderService {
    /**
     * 代理对象
     */
    private OrderService proxiedOrderService;

    public OrderServiceProxy( OrderService orderService) {
      this.proxiedOrderService=orderService;
    }

    public void order() {
        System.out.println("日志收集开始..");
        proxiedOrderService.order();
        System.out.println("日志收集结束..");
    }
}
public class ClientTest {
    public static void main(String[] args) {
        OrderService orderService = new OrderServiceProxy(new OrderServiceImpl());
        orderService.order();
    }
}

接口继承方式实现

public class OrderServiceProxy extends OrderServiceImpl {

    public void order() {
        System.out.println("日志收集开始..");
        super.order();
        System.out.println("日志收集结束..");
    }
}

public class ClientTest {
    public static void main(String[] args) {
        OrderService orderService = new OrderServiceProxy();
        orderService.order();
    }
}

动态代理

动态代理是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。

动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成 。

JDK动态代理

JDK动态代理的一般步骤如下:

1.创建被代理的接口和类;

2.实现InvocationHandler接口,对目标接口中声明的所有方法进行统一处理;

3.调用Proxy的静态方法,创建代理类并生成相应的代理对象;

public interface OrderService {
    void order();
}
public class OrderServiceImpl implements OrderService {
    public void order() {
        System.out.println("修改数据库订单操作..");
    }
}


public class JdkInvocationHandler implements InvocationHandler {
    /**
     * 目标代理对象
     */
    public Object target;

    public JdkInvocationHandler(Object target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(">>>日志收集开始>>>>");
        // 执行代理对象方法
        Object reuslt = method.invoke(target, args);
        System.out.println(">>>日志收集结束>>>>");
        return reuslt;
    }

    /**
     * 获取代理对象接口
     *
     * @param <T>
     * @return
     */
    public <T> T getProxy() {
        return (T) 
            Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

}
Main(){
    JdkInvocationHandler jdkInvocationHandler = new JdkInvocationHandler(new OrderServiceImpl());
	OrderService proxy = jdkInvocationHandler.getProxy();
	proxy.order();
}

CGLIB动态代理

Cglib是一个强大的,高性能,高质量的代码生成类库。它可以在运行期扩展JAVA类与实现JAVA接口。其底层实现是通过ASM字节码处理框架来转换字节码并生成新的类。大部分功能实际上是ASM所提供的,Cglib只是封装了ASM,简化了ASM操作,实现了运行期生成新的class。

CGLIB原理

运行时动态的生成一个被代理类的子类(通过ASM字节码处理框架实现),子类重写了被代理类中所有非final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势植入横切逻辑。

Cglib优缺点

优点:JDK动态代理要求被代理的类必须实现接口,当需要代理的类没有实现接口时Cglib代理是一个很好的选择。另一个优点是Cglib动态代理比使用java反射的JDK动态代理要快
缺点:对于被代理类中的final方法,无法进行代理,因为子类中无法重写final函数

CGLIB代理实现

实现MethodInterceptor接口的intercept方法后,所有生成的代理方法都调用这个方法。
intercept方法的具体参数有
obj 目标类的实例

  1. method 目标方法实例(通过反射获取的目标方法实例)
  2. args 目标方法的参数
  3. proxy 代理类的实例
    该方法的返回值就是目标方法的返回值
public class OrderServiceImpl {
    public void order() {
        System.out.println("用户下单操作..");
    }
}

public class CglibMethodInterceptor implements MethodInterceptor {
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("<<<<<日志收集开始...>>>>>>>");
        Object reuslt = proxy.invokeSuper(obj, args);
        System.out.println("<<<<<日志收集结束...>>>>>>>");
        return reuslt;
    }
}
public static void main(String[] args) {
	System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
	CglibMethodInterceptor cglibMethodInterceptor = new CglibMethodInterceptor();
	Enhancer enhancer = new Enhancer();
	// 设置代理类的付类
	enhancer.setSuperclass(OrderServiceImpl.class);
	// 设置回调对象
	enhancer.setCallback(cglibMethodInterceptor);
	// 创建代理对象
	OrderServiceImpl orderServiceImpl = (OrderServiceImpl) enhancer.create();
	orderServiceImpl.order();
}

静态代理与动态代理区别

静态代理需要自己写代理类,而动态代理不需要写代理类。

JDK动态代理与CGLIB实现区别

JDK动态代理底层实现:

JDK的动态代理使用Java的反射技术生成动态代理类,只能代理实现了接口的类, 没有实现接口的类不能实现动态代理。

CGLIB动态代理底层实现:

运行时动态的生成一个被代理类的子类(通过ASM字节码处理框架实现),子类重写了被代理类中所有非final的方法,在子类中采用方法拦截的技术拦截所有父类方法的调用,不需要被代理类对象实现接口,从而CGLIB动态代理效率比Jdk动态代理反射技术效率要高。

观察者模式

什么是观察者模式

在对象之间定义了一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象会收到通知并自动更新。
其实就是发布订阅模式,发布者发布信息,订阅者获取信息,订阅了就能收到信息,没订阅就收不到信息。

观察者模式应用场景

Zookeeper事件通知节点、消息订阅通知、安卓开发事件注册
分布式配置中心

观察者模式原理类图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zHnAEDmM-1679558936891)(assets/wps1.jpg)]

  1. 抽象被观察者角色:也就是一个抽象主题,它把所有对观察者对象的引用保存在一个集合中,每个主题都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者角色。一般用一个抽象类和接口来实现。
  2. 抽象观察者角色:为所有的具体观察者定义一个接口,在得到主题通知时更新自己。
  3. 具体被观察者角色:也就是一个具体的主题,在集体主题的内部状态改变时,所有登记过的观察者发出通知。
  4. 具体观察者角色:实现抽象观察者角色所需要的更新接口,一边使本身的状态与制图的状态相协调。

Subject ObServer

使用方式(案例)

使用观察者模式模拟微信公众号(简单实现)

美特教育 服务号 两个订阅者 — > 小薇、小敏

1、定义抽象观察者 ObServer
public interface ObServer {

    /**
     * 更新消息内容
     *
     * @param message
     * @return
     */
    public void update(String message);

}
2、抽象主题者 AbstractSubject
public interface AbstractSubject {
    /**
     * 添加obServer  注册观察者
     * @param obServer
     */
    void addObServer(ObServer obServer);

    /**
     * 移除obServer
     * @param obServer
     */
    void removeObServer(ObServer obServer);

    /**
     * 通知所有的notifyObServerAll
     * @param message
     */
    void notifyObServerAll(String message);

    /**
     * 设置更新内容
     */
    void setNtifyMessage(String message);
}

3、具体主题 WeChatSubject
public class WeChatSubject implements AbstractSubject {
    /**
     * 存放所有的ObServer
     */
    private List<ObServer> listObServer = new ArrayList<ObServer>();
    /**
     * 更新的内容
     */
    private String message;

    public void addObServer(ObServer obServer) {
        //注册或者添加观察者
        listObServer.add(obServer);
    }

    public void removeObServer(ObServer obServer) {
        listObServer.remove(obServer);
    }

    public void notifyObServerAll(String message) {
        //调用观察者通知方案
        for (int i = 0; i < listObServer.size(); i++) {
            ObServer obServer = listObServer.get(i);
            obServer.update(message);
        }
    }

    public void setNtifyMessage(String message) {
        this.message = message;
        System.out.println("微信公众号设置message:" + message);
        notifyObServerAll(message);

    }
}
4、具体观察者 UserObServer
public class UserObServer implements ObServer {
    /**
     * 订阅者用户名称
     */
    private String name;
    /**
     * 发送内容
     */
    private String message;

    public UserObServer(String name) {
        this.name = name;
    }
    public void update(String message) {
        this.message = message;
        read();
    }

    public void read() {
        System.out.println(name + ",老师收到推送消息:" + message);
    }
}
5、运行测试
// 1.注册主题
AbstractSubject weChatSubject = new WeChatSubject();
// 2.添加观察者 订阅主题
weChatSubject.addObServer(new UserObServer("小薇"));
weChatSubject.addObServer(new UserObServer("小敏"));
// 3.设置发送消息
weChatSubject.setNtifyMessage("每特教育第五期平均就业薪资破3万+");

JDK自带观察实现消息发送

  1. Observable类追踪所有的观察者,并通知他们。
  2. Observer这个接口看起来很熟悉,它和我们之前写的类几乎一样。
1、自定义主题 MessageObServable
public class MessageObServable extends Observable {

    @Override
    public void notifyObservers(Object arg) {
        // 1.改变数据 (修改状态可以群发)
        setChanged();
        // 2.通知所有的观察者改变--调用父类的notifyObservers 群发消息
        super.notifyObservers(arg);
    }
}
2、自定义观察者 EmailObServer、SmsObServer
public class EmailObServer implements Observer {
    public void update(Observable o, Object arg) {
        // 1.获取主题
        MessageObServable messageObServable = (MessageObServable) o;
        System.out.println("发送邮件内容:" + arg);
    }
}
public class SmsObServer implements Observer {
    public void update(Observable o, Object arg) {
        System.out.println("发送短信内容:" + arg);
    }
}
3、运行监听开始 JdkObServer
public class JdkObServer {
    public static void main(String[] args) {
        //1.创建主题
        MessageObServable messageObServable = new MessageObServable();
        // 2.添加订阅者
        messageObServable.addObserver(new EmailObServer());
        messageObServable.addObserver(new SmsObServer());
        // 3.组装消息内容
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("email", "[email protected]");
        jsonObject.put("phone", "15123456789");
        jsonObject.put("text", "恭喜您以1399.00元购买蚂蚁课堂永久会员.");
        messageObServable.notifyObservers(jsonObject.toJSONString());
    }
}

外观(门面、包装)模式

什么是外观模式

外观模式(Facade),他隐藏了系统的复杂性,并向客户端提供了一个可以访问系统的接口。这种类型的设计模式属于结构性模式。为子系统中的一组接口提供了一个统一的访问接口,这个接口使得子系统更容易被访问或者使用。

外观模式应用场景

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-add22syO-1679558936891)(wps1-16647736523883.jpg)]

简单来说,该模式就是把一些复杂的流程封装成一个接口供给外部用户更简单的使用。这个模式中,设计到3个角色。

  1. 门面角色:外观模式的核心。它被客户角色调用,它熟悉子系统的功能。内部根据客户角色的需求预定了几种功能的组合。
  2. 子系统角色:实现了子系统的功能。它对客户角色和Facade时未知的。它内部可以有系统内的相互交互,也可以由供外界调用的接口。
  3. 客户角色:通过调用Facede来完成要实现的功能。

使用方式(案例)

1、需要重构的代码


@Slf4j
public class PayCallbackService {
    // 用户下单成功后,有那些操作?
    // 1.增加支付回调接口日志
    // 2.修改订单数据库状态为已经成功
    // 3.调用积分服务接口 
    // 4.调用消息服务平台服务接口
	//采用外观模式(门面、包装) 使用一个接口封装复杂的业务逻辑流程,让客户端使用起来更加简单
    public boolean callback(Map<String, String> verifySignature) {
        // 1.第一步打印日志信息
        String orderId = verifySignature.get("orderId"); // 获取后台通知的数据,其他字段也可用类似方式获取
        String respCode = verifySignature.get("respCode");
        log.info("orderId:{},respCode:{}", orderId, respCode);
        // 2.修改订单状态为已经支付
        new PaymentTransactionMapper() {
            @Override
            public void updatePaymentStatus() {
                log.info(">>>修改订单状态为已经支付>>>>>");
            }
        }.updatePaymentStatus();
        // 3.调用积分接口增加积分
        HttpClientUtils.doPost("jifen.com", "积分接口");
        // 4.调用消息服务平台提示
        HttpClientUtils.doPost("msg.com", "调用消息接口");
        return true;
    }
}

2、重构代码–创建业务逻辑封装

@Component
@Slf4j
public class LogService {

    public void logService(Map<String, String> verifySignature) {
        // 1.第一步打印日志信息
        String orderId = verifySignature.get("orderId"); // 获取后台通知的数据,其他字段也可用类似方式获取
        String respCode = verifySignature.get("respCode");
        log.info("第一个模块>>>orderId:{},respCode:{}", orderId, respCode);
    }

}
@Slf4j
@Component
public class PaymentService {

    public void updatePaymentStatus() {
        // 2.修改订单状态为已经支付
        new PaymentTransactionMapper() {
            @Override
            public void updatePaymentStatus() {
                log.info("第二个模块>>>修改订单状态为已经支付>>>>>");
            }
        }.updatePaymentStatus();
    }
}
@Component
@Slf4j
public class IntegralService {

    public void callIntegral() {
        // 3.调用积分接口增加积分
        HttpClientUtils.doPost("jifen.com", "积分接口");
        log.info("第三个模块>>>调用积分接口打印日志>>>>>");
    }
}

@Component
@Slf4j
public class MsgService {

    public void msgService() {
        log.info("第四个模块>>>调用消息模块打印日志>>>>>");
    }
}

3、创建门面接口

@Component
public class PayCallbackFacade {
    @Autowired
    private LogService logService;
    @Autowired
    private PaymentService paymentService;
    @Autowired
    private IntegralService integralService;
    @Autowired
    private MsgService msgService;

    public boolean callbackFacade(Map<String, String> verifySignature) {
        logService.logService(verifySignature);
        paymentService.updatePaymentStatus();
        integralService.callIntegral();
        msgService.msgService();
        return true;
    }
}

优点和缺点

优点

松散耦合

使得客户端和子系统之间解耦,让子系统内部的模块功能更容易扩展和维护;

简单易用
  • 客户端根本不需要知道子系统内部的实现,或者根本不需要知道子系统内部的构成,它只需要跟Facade类交互即可。
更好的划分访问层次
  • 有些方法是对系统外的,有些方法是系统内部相互交互的使用的。子系统把那些暴露给外部的功能集中到门面中,这样就可以实现客户端的使用,很好的隐藏了子系统内部的细节。

缺点

业务封装增加

状态模式

什么是状态模式

状态模式允许一个对象在其内部状态改变的时候改变其行为。这个对象看上去就像是改变了它的类一样。

状态模式应用场景

  1. 一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。
  2. 操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。 通常,有多个操作包含这一相同的条件结构。State模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。

状态模式与策略模式区别

  • 状态模式重点在各状态之间的切换从而做不同的事情,而策略模式更侧重于根据具体情况选择策略,并不涉及切换。
  • 状态模式不同状态下做的事情不同,而策略模式做的都是同一件事,例如聚合支付平台,有支付宝、微信支付、银联支付,虽然策略不同,但最终做的事情都是支付,也就是说他们之间是可替换的。反观状态模式,各个状态的同一方法做的是不同的事,不能互相替换。
  • 状态模式封装了对象的状态,而策略模式封装算法或策略。因为状态是跟对象密切相关的,它不能被重用;而通过从Context中分离出策略或算法,我们可以重用它们。
  • 在状态模式中,每个状态通过持有Context的引用,来实现状态转移;但是每个策略都不持有Context的引用,它们只是被Context使用。
  1. 排序算法 快速排序 选择排序 冒泡排序 策略
  2. 只要是和状态相关 订单、交易、支付状态。

使用方式 (案例)

1、需要重构的代码

    public String orderState(String state) {
        if (state.equals("0")) {
            return "已经发货";
        }
        if (state.equals("1")) {
            return "正在运送中...调用第三方快递接口 展示 运送信息";
        }
        if (state.equals("2")) {
            return "正在派送中... 返回派送人员信息";
        }
        if (state.equals("3")) {
            return "已经签收,提示给用户快递员评价";
        }
        if (state.equals("4")) {
            return "拒绝签收, 重新开始申请退单";
        }
        if (state.equals("5")) {
            return "订单交易失败,调用短信接口提示 ";
        }
        return "未找到对应的状态";
    }
}

2、重构之后

public interface OrderState {
    public void orderService();
}
@Slf4j
@Component
public class AlreadySignedOrderState implements OrderState {
    @Override
    public void orderService() {
        log.info(">>>切换为已经签收状态..");
    }
}
@Slf4j
@Component
public class InTransitOrderState implements OrderState {
    @Override
    public void orderService() {
        log.info(">>>切换为正在运送状态...");
    }
}
@Slf4j
@Component
public class ShippedAlreadyOrderState implements OrderState {
    public void orderService() {
        log.info(">>>切换为已经发货状态..");
    }
}
public class Test {
    public static void main(String[] args) {
        ContextState contextState = new ContextState(new AlreadySignedOrderState());
        contextState.switchStateOrder();
    }
}
@RestController
public class OrderController{
    @RequestMapping("/order")
    public String order(String stateBeanId){
        //1、使用soring上下文获取bean种对象
        OrderState orderState = SpringUtils.getBean(stateBeanid,OrderState.calss);
        //2、使用上下文切换到不同的状态
        StateContext stateContext = new StateContext(orderState);
        stateContext.switchStateOrder();
        
        //如果写多重if判断的话 整个代码流程 耗时比较长  直接再Spring中精准定位到策略或者是状态的话 Map get 方案底层是数组
        return "success";
    }
}

适配器模式

什么是适配器模式

定义:将一个系统的接口转换成另外一种形式,从而使原来不能直接调用的接口变得可以调用。

手机转接头 苹果充电口和 tp_c充电口 直接的转换

适配器模式角色划分

适配器模式涉及3个角色:
源(Adaptee):需要被适配的对象或类型, 旧版本或者苹果手机插口
适配器(Adapter):连接目标和源的中间对象,相当于插头转换器,新版本与老版本能够实现兼容
目标(Target):期待得到的目标, 新版本或者圆子头耳机
适配器模式包括3种形式:类适配器模式、对象适配器模式、接口适配器模式(或又称作缺省适配器模式)。

适配器模式应用场景

  1. 新老版本接口的兼容

  2. Mybatis多种日志框架的整合

适配器创建的方式

对象适配器(组合模式)
类适配器(继承模式)

使用方式(案例)

快速入门例子

比如早期的时候V1版本订单接口的入参为Map类型,随着业务的更新和迭代在V2版本的时候该订单接口的入参需要支持List的类型? 请问不改变的该接口代码的情况下,如何实现支持List类型。

public class OrderService{
	//v1 版本的时候提供了一个接口,入参是map类型
	//v2 版本能够支持List类型
	public void froOrderMap(Map map) {
    	for (int i = 0; i < map.size(); i++) {
        	// 使用I作为MapKey获取数据
        	String value = (String) map.get(i);
        	System.out.println("value:" + value);
    	}
	}
}

创建Adapter适配器能够支持List类型。

public class ListAdapter extends HashMap {

    //目标对象新版本
    private List list;

    public ListAdapter(List list) {
        this.list = list;
    }

    @Override
    public int size() {
        return list.size();
    }

    @Override
    public Object get(Object key) {
        return list.get(Integer.valueOf(key.toString()).intValue());
    }
}

测试运行结果

public class Test {
    public static void main(String[] args) {
       //1、定义源 老版本
        OrderService orderService = new OrderService;
        ArrayList arraylist = new ArrayList();
        arrayList.add("aaa");
        arrayList.add("bbb")
        //2、使用适配器实现转换
        ListAdapter listAdapter = new ListAdapter(arrayList);
     	//3、可以支持list类型
        orderService.forMap(listAdapter);
    }
}

使用适配器模式实现日志收集

比如设计一个日志收集系统,可能会考虑文件写入、也可能考虑写入MQ、也可能考虑写入数据库等。

1、定义基本实体类

public class LogBean {


    /**
     * 日志ID
     */
    private String logId;
    /**
     * 日志内容
     */
    private String logText;

    public String getLogId() {
        return logId;
    }

    public String getLogText() {
        return logText;
    }

    public void setLogId(String logId) {
        this.logId = logId;
    }

    public void setLogText(String logText) {
        this.logText = logText;
    }


}

public interface LogWriteFileService {


    /**
     * 将日志写入到文件中
     */
    void logWriteFile();


    /**
     * 从本地文件中读取日志
     *
     * @return
     */
    List<LogBean> readLogFile();
}
public interface LogWriteDbService {


    /**
     * 将日志写入到数据库中
     */
    void logWriteDb();


    /**
     * 从数据库中读取日志
     *
     * @return
     */
    List<LogBean> readLogDb();
}

public class LogWriteFileServiceImpl implements LogWriteFileService {
    @Override
    public void logWriteFile() {
        System.out.println(">>>将日志写入文件中...");
    }

    @Override
    public List<LogBean> readLogFile() {
        LogBean log1 = new LogBean();
        log1.setLogId("0001");
        log1.setLogText("Tomcat启动成功..");

        LogBean log2 = new LogBean();
        log2.setLogId("0002");
        log2.setLogText("Jetty启动成功..");
        List<LogBean> listArrayList = new ArrayList<LogBean>();
        listArrayList.add(log1);
        listArrayList.add(log2);
        return listArrayList;
    }


}


public class LogAdapter implements LogWriteDbService {
    private LogWriteFileService logWriteFileService;

    public LogAdapter(LogWriteFileService logWriteFileService) {
        this.logWriteFileService = logWriteFileService;
    }

    @Override
    public void writeDbFile(LogBean logBean) {
        // 1.从文件中读取日志文件
        List<LogBean> logBeans = logWriteFileService.readLogFile();
        // 2.写入到数据库中
        logBeans.add(logBean);
        System.out.println(">>>将数据写入到数据库中..");
        // 3.写入到本地文件中
        logWriteFileService.logWriteFile();
    }
}


类适配器模式
继承模式
//public class ClassAdapter extends LogWriteFileServiceImpl implements LogWriteDbService {
public class ClassAdapter implements LogWriteDbService {
    
    //源  --->组合模式
    private LogWriteFileServiceImpl logWriteFileServiceImpl;
        
    public ClassAdapter(LogWriteFileServiceImpl logWriteFileServiceImpl){
        this.logWriteFileServiceImpl = logWriteFileServiceImpl;
    }
    @Override
    public void writeDbFile(LogBean logBean) {
        // 1.从文件中读取日志文件
        List<LogBean> logBeans = readLogFile();
        // 2.写入到数据库中
        logBeans.add(logBean);
        System.out.println(">>>将数据写入到数据库中..");
        // 3.写入到本地文件中
        logWriteFile();
    }
}

Mbatis 日志收集分析

Java开发中经常用到的日志框架有很多,Log4j、Log4j2、slf4j等等,Mybatis定义了一套统一的日志接口供上层使用,并为上述常用的日志框架提供了相应的适配器

在Mybatis的日志模块中就是使用了适配器模式。Mybatis内部在使用日志模块时,使用了其内部接口 org.apache.ibatis.logging.Log,但是常用的日志框架的对外接口各不相同,Mybatis为了复用和集成这些第三方日志组件,在其日志模块中,提供了多种Adapter,将这些第三方日志组件对外接口适配成org.apache.ibatis.logging.Log,这样Myabtis 就可以通过Log接口调用第三方日志了

适配器模式优缺点

优点

  1. 更好的复用性
  2. 系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。
  3. 更好的扩展性
  4. 在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。

缺点

过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是A接口,其实内部被适配成了B接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。

装饰模式和适配器模式

装饰模式是对内的代码可以经常优化更改

适配器是针对外部的代码,自己的代码一般写完都不会进行更改的

单例模式

什么是单例模式

单例模式确保一个类只有一个实例,并提供一个全局访问点,实现单例模式的方法是私有化构造函数,通过getInstance()方法实例化对象,并返回这个实例

保证在JVM中只有一个实例 幂等
JVM中如何保证实例的幂等问题 保证唯一性

饿汉、懒汉 双重检验

单例模式优缺点

1、单例类只有一个实例
2、共享资源,全局使用
3、节省创建时间,提高性能

缺点:可能存在线程不安全的问题 解决线程安全问题

单例模式应用场景

单例的七种写法

分别是「饿汉」、「懒汉(非线程安全)」、「懒汉(线程安全)」、「双重校验锁」、「静态内部类」、「枚举」和「容器类管理、静态块初始化

饿汉式

优缺点
  1. 优点:先天性线程是安全的,当类初始化的 就会创建该对象
  2. 缺点:如果饿汉式使用过多,可能会影响项目启动的效率问题。
public class SingletonV1 {

    /**
     * 饿汉式 优点:先天性线程是安全的,当类初始化的 就会创建该对象 缺点:如果饿汉式使用过多,可能会影响项目启动的效率问题。
     */
    private static SingletonV1 singletonV1 = new SingletonV1();

    /**
     * 将构造函数私有化 禁止初始化
     */
    private SingletonV1() {

    }

    public static SingletonV1 getInstance() {
        return singletonV1;
    }

    public static void main(String[] args) {
        SingletonV1 instance1 = SingletonV1.getInstance();
        SingletonV1 instance2 = SingletonV1.getInstance();
        System.out.println(instance1 == instance2);

    }
}

懒汉式(线程不安全)

public class SingletonV2 {

    /**
     * 懒汉式 (线程不安全)
     */
    private static SingletonV2 singletonV2;

    private SingletonV2() {

    }
    /**
     * 在真正需要创建对象的时候使用...
     * @return
     */
    public static SingletonV2 getInstance() {
        if (singletonV2 == null) {
            try {
                Thread.sleep(2000);
            } catch (Exception e) {
            }
            singletonV2 = new SingletonV2();
        }
        return singletonV2;
    }

    public static void main(String[] args) {
//        SingletonV2 instance1 = SingletonV2.getInstance();
//        SingletonV2 instance2 = SingletonV2.getInstance();
//        System.out.println(instance1 == instance2);
        // 1.模拟线程不安全
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                public void run() {
                    SingletonV2 instance1 = SingletonV2.getInstance();
                    System.out.println(Thread.currentThread().getName() + "," + instance1);
                }
            }).start();
        }
    }
}

懒汉式(线程安全)

public class SingletonV3 {
    /**
     * 懒汉式 线程安全
     */
    private static SingletonV3 singletonV3;

    private SingletonV3() {

    }

    /**
     * 能够解决线程安全问题,创建和获取实例时都上锁 ,效率非常低,所以推荐使用双重检验锁
     *
     * @return
     */
    public synchronized static SingletonV3 getInstance() {
        try {
            Thread.sleep(2000);
        } catch (Exception e) {
        }
        if (singletonV3 == null) {
            System.out.println("创建实例SingletonV3");
            singletonV3 = new SingletonV3();
        }
        System.out.println("获取SingletonV3实例");
        return singletonV3;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                public void run() {
                    SingletonV3 instance1 = SingletonV3.getInstance();
                    System.out.println(Thread.currentThread().getName() + "," + instance1);
                }
            }).start();
        }
    }

}

双重检验锁(DCL)

既保证了极限性能,又保证了安全。

第一个if是不知道是不是第一次调用,如果为空,就需要创建对象,所以要判断一下属性是否为空。

**但是场景是多线程的 场景下,可能同时有多个线程访问方法的时候都判断为空,可能每个线程都会创建对象,这样就违背了我们的初衷,所以要加上锁。**锁住创建对象的代码块,只能有一个线程拿到锁并创建对象。加上锁之后,其他线程是没办法读到第二个if了

为什么不给整个方法加锁?

锁的力度越小,其他线程阻塞的时间越短。追求更快的性能。

为什么要用volatile修饰?

加锁的锁不住不加锁的,synchronized锁不住第一个if,当某一个线程拿到锁之后,其他线程在第一个if阻塞,volatile让其他线程可见是否已经创建了对象。

如果没有volatile,不能防止指令重排序,可能先把句柄传过去,再产生对象,传过去的句柄还没有创建对象,也为空,所以要再判断一次if。

public class SingletonV4 {
    /**
     * volatile 禁止重排序和 提高可见性
     */
    private volatile static SingletonV4 singletonV4;

    private SingletonV4() {

    }

    public static SingletonV4 getInstance() {
        if (singletonV4 == null) { // 第一次判断如果没有创建对象 开始上锁...
            synchronized (SingletonV4.class) {
                if (singletonV4 == null) { // 当用户抢到锁,判断初始化
                    System.out.println("第一次开始创建实例对象....获取锁啦...");
                    try {
                        Thread.sleep(2000);
                    } catch (Exception e) {
                    }
                    singletonV4 = new SingletonV4();
                }
            }
        }
        return singletonV4;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                public void run() {
                    SingletonV4 instance1 = SingletonV4.getInstance();
                    System.out.println(Thread.currentThread().getName() + "," + instance1);
                }
            }).start();
        }
    }
}

静态内部内形式

public class SingletonV5 {

    private SingletonV5() {
        System.out.println("对象初始...");
    }

    public static SingletonV5 getInstance() {
        return SingletonV5Utils.singletonV5;
    }

    /**
     * 静态内部方式能够避免同步带来的效率问题和有能实现延迟加载
     */
    public static class SingletonV5Utils {
        private static SingletonV5 singletonV5 = new SingletonV5();
    }

    public static void main(String[] args) {
        System.out.println("项目启动成功");
        SingletonV5 instance1 = SingletonV5.getInstance();
        SingletonV5 instance2 = SingletonV5.getInstance();
        System.out.println(instance1 == instance2);
    }
}

枚举形式

枚举形式能够先天性,防止反射和序列化破解单例。

public enum EnumSingleton {
    INSTANCE;

    // 枚举能够绝对有效的防止实例化多次,和防止反射和序列化破解
    public void add() {
        System.out.println("add方法...");
    }
}

public static void main(String[] args) throws Exception {
    EnumSingleton instance1 = EnumSingleton.INSTANCE;
    EnumSingleton instance2 = EnumSingleton.INSTANCE;
    System.out.println(instance1 == instance2);
    Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor();
    declaredConstructor.setAccessible(true);
    EnumSingleton v3 = declaredConstructor.newInstance();
    System.out.println(v3==instance1);
}

使用容器管理

public class SingletonManager {
    private static Map<String, Object> objMap = new HashMap<String, Object>();
    public static void registerService(String key, Object instance) {
        if (!objMap.containsKey(key)) {
            objMap.put(key, instance);
        }
    }
    public static Object getService(String key) {
        {
            return objMap.get(key);
        }
    }
}

这种使用SingletonManager 将多种单例类统一管理,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。

如何防止破坏单例

虽然单例通过私有构造函数,可以实现防止程序猿初始化对象,但是还可以通过反射和序列化技术破解单例。

使用反射技术破解单例

// 1. 使用懒汉式创建对象
SingletonV3 instance1 = SingletonV3.getInstance();
// 2. 使用Java反射技术初始化对象 执行无参构造函数
Constructor<SingletonV3> declaredConstructor = SingletonV3.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
SingletonV3 instance2 = declaredConstructor.newInstance();
System.out.println(instance1 == instance2);

如何防止被反射破解

私有构造函数

private SingletonV3() throws Exception {
    synchronized (SingletonV3.class) {
        if (singletonV3 != null) {
            throw new Exception("该对象已经初始化..");
        }
        System.out.println("执行SingletonV3无参构造函数...");
    }

}

使用序列化技术破解单例

Singleton instance = Singleton.getInstance();
FileOutputStream fos = new FileOutputStream("E:\\code\\Singleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance);
oos.flush();
oos.close();

FileInputStream fis = new FileInputStream("E:\\code\\Singleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
Singleton singleton2 = (Singleton) ois.readObject();
System.out.println(singleton2==instance)



//返回序列化获取对象 ,保证为单例
public Object readResolve() {
    return singletonV3;
}

{
EnumSingleton instance1 = EnumSingleton.INSTANCE;
EnumSingleton instance2 = EnumSingleton.INSTANCE;
System.out.println(instance1 == instance2);
Constructor declaredConstructor = EnumSingleton.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
EnumSingleton v3 = declaredConstructor.newInstance();
System.out.println(v3==instance1);
}


### 使用容器管理

```java
public class SingletonManager {
    private static Map<String, Object> objMap = new HashMap<String, Object>();
    public static void registerService(String key, Object instance) {
        if (!objMap.containsKey(key)) {
            objMap.put(key, instance);
        }
    }
    public static Object getService(String key) {
        {
            return objMap.get(key);
        }
    }
}

这种使用SingletonManager 将多种单例类统一管理,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。

如何防止破坏单例

虽然单例通过私有构造函数,可以实现防止程序猿初始化对象,但是还可以通过反射和序列化技术破解单例。

使用反射技术破解单例

// 1. 使用懒汉式创建对象
SingletonV3 instance1 = SingletonV3.getInstance();
// 2. 使用Java反射技术初始化对象 执行无参构造函数
Constructor<SingletonV3> declaredConstructor = SingletonV3.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
SingletonV3 instance2 = declaredConstructor.newInstance();
System.out.println(instance1 == instance2);

如何防止被反射破解

私有构造函数

private SingletonV3() throws Exception {
    synchronized (SingletonV3.class) {
        if (singletonV3 != null) {
            throw new Exception("该对象已经初始化..");
        }
        System.out.println("执行SingletonV3无参构造函数...");
    }

}

使用序列化技术破解单例

Singleton instance = Singleton.getInstance();
FileOutputStream fos = new FileOutputStream("E:\\code\\Singleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance);
oos.flush();
oos.close();

FileInputStream fis = new FileInputStream("E:\\code\\Singleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
Singleton singleton2 = (Singleton) ois.readObject();
System.out.println(singleton2==instance)



//返回序列化获取对象 ,保证为单例
public Object readResolve() {
    return singletonV3;
}
;