Bootstrap

设计模式第三天|设计模式结构型:适配器模式、装饰器模式、代理模式

设计模式的分类

总的来说设计模式分为三大类

创建型模式(五种):工厂方法模式,抽象工厂模式,单例模式,建造者模式,原型模式。
结构型模式(七种):适配器模式,装饰器模式,代理模式,外观模式,桥接模式,组合模式,享元模式。
行为型模式(十一种):策略模式,模板方法模式,观察者模式,迭代子模式,责任链模式,命令模式,备忘录模式,状态模式,访问者模式,中介者模式,解释器模式。

这篇我们主要讲一下结构型模式。

由于我是java开发者,我会根据设计模式在java中的应用来说说

适配器模式

概念

将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的哪些类可以一起工作。

俗话说

就是安卓充电头(Adaptee原角色)转换成type-c充电头(Target目标角色)要使用转接头(Adapter适配器角色)一样

角色

  1. 目标角色(Target)

该角色定义把其他类转换为何种接口,也就是我们的期望接口。

  1. 源角色(Adaptee)

你想把谁转换成目标角色,这个“谁”就是源角色,它是已经存在的、运行良好的类或对象。

  1. 适配器角色(Adapter)

适配器模式的核心角色,其他两个角色都是已经存在的角色,而适配器角色是需要新建立的,它的职责非常简单:通过继承或是类关联的方式把源角色转换为目标角色。

具体应用(Spring MVC)

图解

在这里插入图片描述

具体步骤

  1. 浏览器发送请求到 控制器(DispatcherServlet)

  2. 控制器 根据请求地址, 到 HandlerMapping(处理器映射) 寻找对应的 Handler(处理器)

  3. HanldlerMapping 返回 找到的Handler

  4. DispatcherServlet 根据找到的Handler 找对应的HandlerAdaptor

  5. 执行对应的Handler方法

  6. Handler 将执行结果 和 要响应的视图名 封装成 ModelAndView 对象

  7. 控制器根据返回的 ViewName 找对应的ViewResolver (视图解析ViewResolver 将 Model 渲染到 View 中

  8. 将渲染结果 返回给控制器

  9. 最终将结果响应给客户端浏览器

适配器使用就是这里的第4,5步.

装饰器模式

定义

是指在不改变原有对象的基础之上,将功能附加到对象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能),属于结构型模式。

核心

装饰器模式的核心是功能扩展,使用装饰器模式可以透明且动态地扩展类的功能。

俗话说

也就跟开闭原则一样,只做扩展,不做改变,追只不过主体编号变成了要加上的功能

类名表现

  1. Wrapper(包装器模式)
    1. (包装类):基础数据类型转引用类型
基本数据类型包装类
intInteger
byteByte
shortShort
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

​ 2. MyBatis-plus中的Wrapper

​ 使用方法

QueryWrapper<User> queryWrapper = new QueryWrapper<>();   queryWrapper.isNull("name").age("age", 12).isNotNull("email");
int result = userMapper.delete(queryWrapper);
  1. Decorator(装饰模式)

如下所示

图解

在这里插入图片描述

具体构造

  1. Component(抽象构件):定义一个抽象接口以规范准备接收附加责任的对象。
  2. ConcreteComponent(具体构件):实现抽象构件,通过装饰角色为其添加一些职责。
  3. Decorator(抽象装饰类):继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
  4. ConcreteDecorator(具体装饰类):实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。

代码实现

大家应该都看过一部日本动画片《铁甲小宝》,里面的一号机器人—卡布达,在平时的时候,他是一个笨笨的但是特别可爱的机器人,当他们需要争夺和平星进行比赛的时候,他就会变身,成为战斗力爆表的卡布达,最后,他获得了他的卡布达巨人,让战斗力又提升了一个档次。这个例子中,变身卡布达以及卡布达巨人就是普通卡布达的装饰,增强卡布达的能力。

  1. 卡布达接口
//Component(抽象构件):定义一个抽象接口以规范准备接收附加责任的对象。
public interface KaBuDa {
 
    /**
     * 示人的形象
     */
    void display();
}
  1. 装饰器代码
//Decorator(抽象装饰类):继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
public class Decorator implements KaBuDa {
 
    private KaBuDa kaBuDa;
 
    public Decorator(KaBuDa kaBuDa) {
        this.kaBuDa = kaBuDa;
    }
 
 
    @Override
    public void display() {
        this.kaBuDa.display();
    }
}
  1. 普通卡布达CommonKaBuDa
//ConcreteComponent(具体构件):实现抽象构件,通过装饰角色为其添加一些职责。
public class CommonKaBuDa implements KaBuDa{
 
    @Override
    public void display() {
        System.out.println("我是普通卡布达,卡布卡布卡布");
    }
}
  1. 变身后的卡布达TransfigurationKaBuDa
//ConcreteDecorator(具体装饰类):实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
public class TransfigurationKaBuDa extends Decorator{
 
    public TransfigurationKaBuDa(KaBuDa kaBuDa) {
        super(kaBuDa);
    }
 
    @Override
    public void display() {
        super.display();
        System.out.println("启动超级变换形态---超级变换形态");
    }
}
  1. 卡布达巨人GiantKaBuDa
//ConcreteDecorator(具体装饰类):实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
public class GiantKaBuDa extends Decorator{
 
    public GiantKaBuDa(KaBuDa kaBuDa) {
        super(kaBuDa);
    }
 
    @Override
    public void display() {
        super.display();
        System.out.println("超级卡布达巨人");
    }
}
  1. 测试

可以看到,这个装饰是一点一点往上加的

public class MainClass {
    public static void main(String[] args) {
        System.out.println("-----------------1--------------------");
        KaBuDa commonKaBuDa = new CommonKaBuDa();
        commonKaBuDa.display();
        System.out.println("-----------------2--------------------");
        System.out.println("蜻蜓队长: 比赛开始");
        System.out.println("-----------------3--------------------");
        KaBuDa transfigurationKaBuDa = new TransfigurationKaBuDa(commonKaBuDa);
        transfigurationKaBuDa.display();
        System.out.println("-----------------4--------------------");
        System.out.println("呼唤卡布达巨人");
        System.out.println("-----------------5--------------------");
        KaBuDa giantKaBuDa = new GiantKaBuDa(transfigurationKaBuDa);
        giantKaBuDa.display();
    }
}
-----------------1--------------------
我是普通卡布达,卡布卡布卡布
-----------------2--------------------
蜻蜓队长: 比赛开始
-----------------3--------------------
我是普通卡布达,卡布卡布卡布
启动超级变换形态---超级变换形态
-----------------4--------------------
呼唤卡布达巨人
-----------------5--------------------
我是普通卡布达,卡布卡布卡布
启动超级变换形态---超级变换形态
超级卡布达巨人

结果看到,对卡布达不断进行进行装饰,最后达到拥有卡布达巨人的无敌状态。

简化

一般情况下,装饰器模式没有抽象的被装饰接口,只有一个具体的被装饰对象,这是就考虑去掉Component类(接口),把Decorator作为一个ConcreteComponent的子类。如下图所示:

在这里插入图片描述

优点

  1. 装饰器是继承的有力补充,比继承灵活,不改变原有对象的情况下动态地给一个对象扩展功能,即插即用

  2. 通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同效果

  3. 装饰器完全遵守开闭原则

缺点

  • 从代码层面来看,使用装饰器模式会出现更多的代码,更多的类,增加程序复杂性
  • 动态装饰时,多层装饰时会更复杂

代理模式(Spring AOP 面向切面)

定义

是为某个对象提供一个代理对象,并且由代理对象控制对原对象的访问。代理模式通俗来讲就是我们生活中常见的中介。

俗话说

  1. 你要一个产品,这个产品(真实角色)是从某个模子(抽象角色)里刻出来的,然后你又需要这个产品有不一样的地方,需要一个次加工(代理角色).然后你最终拿到这个产品(代理角色)是从次加工那里拿到的
  2. 通知的用法:小宝宝想睡觉(service层中某个方法),但是睡前要脱衣服(方法前通知),睡醒还要穿衣服(方法执行后通知),于是脱衣服和穿衣服都可以写进(切面)
  3. 只不过这个切面不是属于框架中的,而是想要给框架中某一部分添加一些功能

角色

  1. 抽象角色(Subject):通过接口或抽象类声明真实角色实现的业务方法。
  2. 代理角色(Proxy):实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
  3. 真实角色(RealSubject):实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。

代理模式分类

静态代理

角色
  • 抽象角色:一般会使用接口或者抽象类来解决
  • 真实角色:被代理的角色
  • 代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作
  • 客户:访问代理对象的人!
代码
  1. 抽象角色
//出租房子
public interface Rent {
    void rent();
}
  1. 真实角色
//房东
public class Host implements Rent {
    @Override
    public void rent() {
        //打印
        System.out.println("房东有房子出租!");
    }
}
  1. 代理角色
//中介
public class Proxy implements Rent {
    private Host host;

    public Proxy() {
    }

    public Proxy(Host host) {
        this.host = host;
    }

    @Override
    public void rent() {
        host.rent();
        seeHouse();
        hetong();
        fare();
    }

    public void seeHouse() {
        //打印
        System.out.println("See House 看房子");
    }

    public void hetong(){
        //打印
        System.out.println("签约租凭合同");
    }
    public void fare(){
        //打印
        System.out.println("收取中介费用!");
    }
}
  1. 客户端访问代理角色
public class Client {
    public static void main(String[] args) {
        //房东出租房子给中介代理
        Host host = new Host();
        //代理。帮助房东出租房子,会有一些附属的操作
        Proxy proxy = new Proxy(host);
        //
        proxy.rent();
    }
}
好处
  • 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
  • 公共也就就交给代理角色!实现了业务的分工!
  • 公共业务发生扩展的时候,方便集中管理!
缺点

一个真实角色就会产生一个代理角色;代码量会翻倍开发效率会变低

实用
  1. UserService
public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void query();
}
  1. UserServiceImpl——真实对象
//真实对象
public class UserServiceImpl implements UserService{
    @Override
    public void add() {
        System.out.println("增加了一个用户");
    }

    @Override
    public void delete() {
        System.out.println("删除了一个用户");
    }

    @Override
    public void update() {
        System.out.println("修改了一个用户");
    }

    @Override
    public void query() {
        System.out.println("查询了一个用户");
    }
}
  1. UserServiceProxy——代理
public class UserServiceProxy implements UserService{

    private UserServiceImpl userService;

    public void setUserService(UserServiceImpl userService) {
        this.userService = userService;
    }

    @Override
    public void add() {
        log("add");
        userService.add();
    }

    @Override
    public void delete() {
        log("delete");
        userService.delete();
    }

    @Override
    public void update() {
        log("update");
        userService.update();
    }

    @Override
    public void query() {
        log("query");
        userService.query();
    }

    //日志方法
    public void log(String msg){
        System.out.println("使用了"+msg+"方法");
    }
}

  1. 客户端
public class Client {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
        UserServiceProxy proxy = new UserServiceProxy();
        proxy.setUserService(userService);
        proxy.add();
    }
}

动态代理

动态代理和静态代理角色一样

动态代理的代理类是动态生成的,不是我们直接写好的!

此处留白,作者还在学习

AOP

什么是AOP

  1. 面向切面编程(方面),利用AOP可以对业务逻辑的各个部分进行隔离,从而是的业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
  2. 通俗描述:不通过修改源代码方式,在主干功能里面添加新功能
  3. 使用登录的例子说明AOP

在这里插入图片描述

具体信息

AOP的作用: 提供声明式事务,允许用户自定义切面.

  1. Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。(增强的集合))
  2. Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。(要增强的方法)
  3. Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。(是在方法执行的时机)
  4. Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。(具体要怎么增强)
  5. Target(目标对象):织入 Advice 的目标对象.。(具体要增强的类)
  6. Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程(增强的集合与目标对象的联系)

通知类型

  1. 前置通知 Before advice:在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常
  2. 后置通知 After returning advice:在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行
  3. 异常通知 After throwing advice:在连接点抛出异常后执行
  4. 最终通知 After (finally) advice:在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容
  5. 环绕通知 Around advice:环绕通知围绕在连接点前后,能在方法调用前后自定义一些操作,还需要负责决定是继续处理 join point (调用 ProceedingJoinPoint 的 proceed 方法)还是中断执行

AOP底层原理

AOP底层使用动态代理,有两种情况

  1. 有接口的情况,使用JDK动态代理;创建接口实现类代理对象,增强类的方法
    在这里插入图片描述

  2. 没有接口情况,使用CGLIB动态代理;创建子类的代理对象,增强类的方法

在这里插入图片描述

实际用法

给某层创建一个切面,并且在这个切面中放入一些方法(增强)

在这里插入图片描述

AOP的JDK动态代理

  1. 使用JDK动态代理,使用Proxy类里面的方法创建代理对象

调用 newProxyInstance 方法,方法有三个参数:

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)

方法有三个参数:

第一参数,类加载器

第二参数,增强方法所在的类,这个类实现的接口,支持多个接口

第三参数,实现这个接口 InvocationHandler,创建代理对象,写增强的部分

  1. 编写JDK动态代理

    1. 创建接口,定义方法
    public interface UserDao {
        public int add(int a,int b);
        public String update(String id);
    
    }
    
    1. 创建接口实现类,实现方法
    public class UserDaoImpl implements UserDao{
        @Override
        public int add(int a,int b) {
            return a+b;
        }
    
        @Override
        public String update(String id) {
            return id;
        }
    
    }
    
    1. 使用Proxy类创建接口代理对象
    //创建代理对象代码
    public class UserDaoProxy implements InvocationHandler {
    
        //把创建的是谁的代理对象,把谁传递过来
        //有参构造传递
        private Object obj;
    
        public UserDaoProxy(Object obj) {
            this.obj = obj;
        }
    
        //增强的逻辑
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
             //方法之前
             System.out.println("方法之前执行..."+method.getName()+":传递的参数..."+ Arrays.toString(args));
    
             //被增强的方法执行
             Object res = method.invoke(obj, args);
    
             //方法之后
             System.out.println("方法之后执行..."+obj);
    
            return res;
        }
    }
    
    1. 客户端
    //使用 Proxy 类创建接口代理对象
    public class JDKProxy {
        public static void main(String[] args) {
    
            //创建接口实现类代理对象
            Class[] interfaces = {UserDao.class};
    
            UserDaoImpl userDao = new UserDaoImpl();
            UserDao dao  = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
    
            int res = dao.add(1, 2);
            System.out.println("result:"+res);
        }
    
    }
    

回过头来我们再看看AOP的具体信息

  1. 连接点

类里面哪些方法可以被增强,这些方法称为连接点

  1. 切入点

实际被真正增强的方法,称为切入点

  1. 通知(增强)
    1. 实际增强的逻辑部分称为通知(增强)
    2. 通知有多种类型,在上边有介绍
  2. 切面

把通知应用到切入点过程

AOP操作

准备环节
  1. Spring框架一般都是基于AspectJ实现AOP操作

AspectJ不是Spring组成部分,独立AOP框架,一般把AspectJ和Spring框架一起使用,进行AOP操作

  1. 基于AspectJ实现AOP操作

  2. 基于xml配置文件实现

  3. 基于注解方式实现(使用)

  4. 在项目工程中引入AOP相关依赖

		<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.2.15.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.9</version>
        </dependency>

        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>
        
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjtools</artifactId>
            <version>1.9.9</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>
  1. 切入点表达式

    1. 切入点表达式作用:知道对哪个类里面的哪个方法进行增强

    2. 语法结构

      • execution (【权限修饰符】【返回类型】【类全路径】【方法名称】(【参数列表】)
      • 举例1:对com.jin.dao.UserDao类里面的add进行增强

      execution(* com.jin.dao.UserDao.add(..))
      
      • 举例2:对com.jin.dao.UserDao类里面的所有的方法进行增强
      execution(* com.jin.dao.UserDao.*(..))
      
      • 举例3:对com.jin.dao类里面所有类,类里面的所有的方法进行增强
      execution(* com.jin.dao.*.*(..))
      
操作环节(AspectJ注解)
  1. 创建类,在类中定义方法
//被增强的类
public class User {
    public void add(){
        System.out.println("add ...");
    }
}
  1. 创建增强类(编写增强逻辑)

    1. 在增强类里面,创建方法,让不同方法代表不同通知类型
    //增强类
    public class UserProxy {
        //前置通知
        public void before(){
            System.out.println("before ...");
        }
    }
    
  2. 进行通知的配置

    1. 在spring配置文件中,开启注解扫描
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop.xsd
    ">
        <!--开启注解扫描-->
    <context:component-scan base-package="com.jin.aopanno"></context:component-scan>
    </beans>
    
    
    1. 使用注解创建User和UserProxy对象
    //被增强的类
    @Component
    public class User {
       ...
    }
    
    //增强类
    @Component
    public class UserProxy {
       ...
    }
    
    1. 在增强类上面添加注解@Aspect
    //增强类
    @Component
    @Aspect   //生成代理对象
    public class UserProxy {
     	...   
    }
    
    1. 在spring配置文件中开启生成代理对象
    <!--开启Aspect生成代理对象-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    
  3. 配置不同类型的通知

    1. 在增加类的里面,通知方法上面添加通知类型注解,使用切入点表达式配置
    //增强类
    @Component
    @Aspect   //生成代理对象
    public class UserProxy {
        //前置通知
        //@Before注解表示作为前置通知
        @Before(value = "execution(* com.jin.aopanno.User.add(..))")
        public void before(){
            System.out.println("before ...");
        }
        @AfterReturning(value = "execution(* com.jin.aopanno.User.add(..))")
        public void afterReturning(){
            System.out.println("afterReturning ...");
        }
        @After(value = "execution(* com.jin.aopanno.User.add(..))")
        public void after(){
            System.out.println("after ...");
        }
    
    
        @AfterThrowing(value = "execution(* com.jin.aopanno.User.add(..))")
        public void afterThrowing(){
            System.out.println("AfterThrowing ...");
        }
        @Around(value = "execution(* com.jin.aopanno.User.add(..))")
        public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            System.out.println("环绕之前 ...");
            proceedingJoinPoint.proceed();
            System.out.println("环绕之后 ...");
        }
    }
    
  4. 相同的切入点抽取

//增强类
@Component
@Aspect   //生成代理对象
public class UserProxy {

	//相同切入点抽取
    @Pointcut(value = "execution(* com.jin.aopanno.User.add(..))")
    public void pointdemo(){}
    
    //前置通知
    //@Before注解表示作为前置通知
    @Before(value = "pointdemo()")
    public void before(){
        System.out.println("before ...");
    }
}
  1. 有多个增强类多同一个方法进行增强,设置增强类优先级

    1. 在增强类上边添加注解@Order(数字类型值),数字类型值越小优先级越高
    @Component
    @Aspect   //生成代理对象
    @Order(1)  //优先级(数字值越小优先级越高)
    public class PersonProxy {
       ...
    }
    
  2. 完全使用注解开发

    1. 创建配置类,不需要创建xml配置文件
    @ComponentScan(basePackages = {"com.jin.pojo"})
    @Configuration
    @EnableAspectJAutoProxy(proxyTargetClass = true) //默认EnableAspectJAutoProxy为false
    public class SpringConfig {
    }
    
    1. 测试代码
    @Test
        public void MyTest01(){
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
            User user = context.getBean("user", User.class);
            user.add();
        }
    
操作环节(AspectJ配置文件)
  1. 创建两个类,增强类和被增强类,创建方法
//被增强类
public class Book {
    public void buy(){
        System.out.println("buy ....");
    }
}
//增强类
public class BookProxy {

    public void before(){
        System.out.println("before ...");
    }
}
  1. 在Spring配置文件中创建两个类对象
<!--创建对象-->
<bean id="book" class="com.jin.aop.Book"></bean>
<bean id="bookProxy" class="com.jin.aop.BookProxy"></bean>
  1. 在Spring配置文件中配置切入点
<!--配置aop增强-->
<aop:config>
    <!--切入点-->
    <aop:pointcut id="p" expression="execution(* com.jin.aop.Book.buy(..))"/>
    <!--配置切面-->
    <aop:aspect ref="bookProxy">
        <!--增强作用在具体的方法上-->
        <aop:before method="before" pointcut-ref="p"/>
    </aop:aspect>
</aop:config>
  1. 测试
@Test
    public void MyTest(){
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        Book book = context.getBean("book", Book.class);
        book.buy();
    }
}
;