欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos

本篇概览

  • 本篇是《quarkus依赖注入》系列的第八篇,目标是掌握quarkus实现的一个CDI特性:装饰器(Decorator)
  • 提到装饰器,熟悉设计模式的读者应该会想到装饰器模式,个人觉得下面这幅图很好的解释了装饰器模式,左下角的红框是关键点:自己的send方法中,先调用父类的send(也就是被装饰类的send),然后才是自己的业务逻辑

  • quarkus也支持装饰器模式,通过注解DecoratorDelegate实现,今天咱们就通过实战掌握如何在quarks框架下通过装饰器扩展应用
  • quarkus是按照CDI的标准来支持装饰器模式的,下图来自官方文档

  • 接下来进入实战环节

实战功能说明

  • 网上讲述装饰器模式的文章中,有个咖啡价格的例子非常经典,如下图所示:
  1. 一杯意式浓缩咖啡(Espresso)价格3美元
  2. 拿铁(Latte)由意式浓缩+牛奶组成,价格是意式浓缩和牛奶之和,即5美元
  3. 焦糖玛奇朵(CaramelMacchiato)由拿铁+焦糖组成,价格比拿铁多了焦糖的1美元,即6美元
  4. 每种咖啡都是一种对象,价格由getPrice方法返回

  • 在上述场景中,当咖啡的内容不断丰富,咖啡价格也要做相应调整,装饰器的作用是让代码优雅的应对变化,对内代码整洁低耦合,对外保持统一接口getPrice

  • 装饰器模式本身并不是本篇的重点,咱们还是聚焦quarkus下的装饰器功能:在咖啡价格的基础上,通过装饰器计算出拿铁的价格

  • 接下来开始编码

编码实战

  • 首先定义接口Coffee.java,不论是意式浓缩、拿铁、还是其他种类,对外都称之为Coffee,都有getPrice方法
package com.bolingcavalry.decorator;

public interface Coffee {

    /**
* 咖啡名称
* @return
*/
String name(); /**
* 当前咖啡的价格
* @return
*/
int getPrice();
}
  • 然后是最基础的意式浓缩咖啡,非常简单的一个bean,定价3美元,这里有个细节要注意:name方法中写死了字符串Espresso,而没用getClass().getSimpleName(),这是因为在quarkus容器中,Espresso的bean并非Espresso类型,而是动态生成的代理类,所以getClass返回的类不是Espresso
package com.bolingcavalry.decorator.impl;

import com.bolingcavalry.decorator.Coffee;

import javax.enterprise.context.ApplicationScoped;

/**
* 意式浓缩咖啡,价格3美元
*/
@ApplicationScoped
public class Espresso implements Coffee { @Override
public String name() {
return "Espresso";
} @Override
public int getPrice() {
return 3;
}
}
  • 接下来就是重点了,拿铁,由意式浓缩+牛奶组成,代码如下,有几处要注意的地方稍后会提到
package com.bolingcavalry.decorator.impl;

import com.bolingcavalry.decorator.Coffee;
import io.quarkus.arc.Priority;
import io.quarkus.logging.Log; import javax.decorator.Decorator;
import javax.decorator.Delegate;
import javax.inject.Inject; @Decorator
@Priority(11)
public class Latte implements Coffee {
/**
* 牛奶价格:2美元
*/
private static final int MILK_PRICE = 2; @Delegate
@Inject
Coffee delegate; @Override
public String name() {
return "Latte";
} @Override
public int getPrice() {
// 将Latte的代理类打印出来,看quarkus注入的是否正确
Log.info("Latte's delegate type : " + this.delegate.name());
return delegate.getPrice() + MILK_PRICE;
}
}
  • 上述代码有以下几处要注意
  1. 先明确目的:我们设计Latte这个bean,本意是通过装饰器模式来装饰Espresso,因此才会用到quarkus的装饰器功能
  2. 使用quarkus的装饰器功能时,有两件事必须要做:装饰类要用注解Decorator修饰,被装饰类要用注解Delegate修饰
  3. 因此,Latte被注解Decorator修饰,Latte的成员变量delegate是被装饰类,要用注解Delegate修饰,
  4. Latte的成员变量delegate并未指明是Espresso,quarkus会选择Espresso的bean注入到这里
  5. 在getPrice方法中打印出delegate.name方法的返回值,验证delegate的身份,以确认quarkus注入的是否正确
  6. 注解Priority很重要,留在接下来的CaramelMacchiato类(焦糖玛奇朵)写完后再说清楚
  • 接下来是CaramelMacchiato类(焦糖玛奇朵),有几处要注意的地方稍后会说明
package com.bolingcavalry.decorator.impl;

import com.bolingcavalry.decorator.Coffee;
import io.quarkus.arc.Priority;
import io.quarkus.logging.Log; import javax.decorator.Decorator;
import javax.decorator.Delegate;
import javax.inject.Inject; /**
* 焦糖玛奇朵:拿铁+焦糖
*/
@Decorator
@Priority(10)
public class CaramelMacchiato implements Coffee { /**
* 焦糖价格:1美元
*/
private static final int CARAMEL_PRICE = 1; @Delegate
@Inject
Coffee delegate; @Override
public String name() {
return "CaramelMacchiato";
} @Override
public int getPrice() {
// 将CaramelMacchiato的代理类打印出来,看quarkus注入的是否正确
Log.infov("CaramelMacchiato's delegate type : " + this.delegate.name());
return delegate.getPrice() + CARAMEL_PRICE;
}
}
  • CaramelMacchiato代码的逻辑和Latte的差不多,都用了注解Decorator和Delegate,目的是为了做Latte的装饰器
  • 重点关注的是成员变量delegate,其类型、名称、注解,都和Latte的delegate一模一样:
@Delegate
@Inject
Coffee delegate;

重要知识点

  • 看到这里,相信您也发现了问题所在:CaramelMacchiato和Latte都有成员变量delegate,其注解和类型声明都一模一样,那么,如何才能保证Latte的delegate注入的是Espresso,而CaramelMacchiato的delegate注入的是Latte呢?
  • 此刻就是注解Priority在发挥作用了,CaramelMacchiato和Latte都有注解Priority修饰,属性值却不同,属性值越大越接近原始类Espresso,如下图,所以,Latte装饰的就是Espresso,CaramelMacchiato装饰的是Latte

单元测试类

  • 最后是单元测试类,成员变量的类型是Coffee,也就是说quarkus容器会自动注入装饰过的CaramelMacchiato类型的bean,而testDecoratorPrice方法中断言coffee.getPrice()的值等于6,如果注入caffee的bean不是CaramelMacchiato类型,断言就会失败
package com.bolingcavalry;

import com.bolingcavalry.decorator.Coffee;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test; import javax.inject.Inject; @QuarkusTest
public class DecoratorTest { @Inject
Coffee coffee; @Test
public void testDecoratorPrice() {
Assertions.assertEquals(6, coffee.getPrice());
}
}

验证

  • 执行单元测试,如下图,单元测试通过表示coffee注入的是CaramelMacchiato类型的bean,再看右侧的日志,CaramelMacchiato的成员变量delegate是Latte类型,Latte的成员变量delegate是Espresso类型,都按照咱们的预期准确注入了

  • 紧接着再做个尝试:将Latte的注解Priority的属性值改小,小于CaramelMacchiato的10,如下图红框,如此一来,CaramelMacchiato的优先级更大,因此更靠近Espresso,由它去装饰Espresso,Latte离Espresso更远,所以它装饰的是CaramelMacchiato

  • 再次运行单元测试,如下图,首先测试依旧能通过,这个好理解,无论装饰逻辑怎么变,最终的bean的getPrice返回值,都是意式浓缩+牛奶+焦糖的价格之和,然后在看右侧日志信息,果然,CaramelMacchiato注入的成员变量是Espresso,Latte注入的成员变量是CaramelMacchiato

  • 至此,装饰器的编码实战已完成,相信您可以在应用中用熟练使用装饰器来扩展bean能力,并且保持与原有bean之间的代码低耦合

与拦截器的不同

  • 如果您看过《拦截器》一文,应该会发现,同样的功能用拦截器也能实现,那为何还要多出个装饰器呢?
  • 其实网上也有类似的讨论,首先是Stack Overflow上分析,一个高赞的观点是:通常情况下,一个装饰器被用于一个特定类上,而拦截器用于拦截多个类
  • 这篇2012年的关于CDI的文章《Interceptors and Decorators tutorial》中的对比更好理解:

  • 个人理解:
  1. 拦截器适合做一些通用的事情,例如日志、异常处理等,可以为多个bean服务
  2. 装饰器适合做特定的事情,例如本篇的演示代码中,计算价格是被装饰类的特性,其他bean没有这个功能,所以装饰器也只能用在,作为核心功能的增强或者完善

欢迎关注博客园:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴...

quarkus依赖注入之八:装饰器(Decorator)的更多相关文章

  1. python函数编程-装饰器decorator

    函数是个对象,并且可以赋值给一个变量,通过变量也能调用该函数: >>> def now(): ... print('2017-12-28') ... >>> l = ...

  2. python 装饰器(decorator)

    装饰器(decorator) 作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 装饰器(decorator)是一种高级Python语 ...

  3. python语法32[装饰器decorator](转)

    一 装饰器decorator decorator设计模式允许动态地对现有的对象或函数包装以至于修改现有的职责和行为,简单地讲用来动态地扩展现有的功能.其实也就是其他语言中的AOP的概念,将对象或函数的 ...

  4. Python的程序结构[8] -> 装饰器/Decorator -> 装饰器浅析

    装饰器 / Decorator 目录 关于闭包 装饰器的本质 语法糖 装饰器传入参数 1 关于闭包 / About Closure 装饰器其本质是一个闭包函数,为此首先理解闭包的含义. 闭包(Clos ...

  5. Python_高阶函数、装饰器(decorator)

    一.变量: Python支持多种数据类型,在计算机内部,可以把任何数据都看成一个“对象”,而变量就是在程序中用来指向这些数据对象的,对变量赋值就是把数据和变量给关联起来. 对变量赋值x = y是把变量 ...

  6. python 语法之 装饰器decorator

    装饰器 decorator 或者称为包装器,是对函数的一种包装. 它能使函数的功能得到扩充,而同时不用修改函数本身的代码. 它能够增加函数执行前.执行后的行为,而不需对调用函数的代码做任何改变. 下面 ...

  7. angular中自定义依赖注入的方法和decorator修饰

    自定义依赖注入的方法 1.factory('name',function () { return function(){ } }); 2.provider('name',function(){ thi ...

  8. Day4 闭包、装饰器decorator、迭代器与生成器、面向过程编程、三元表达式、列表解析与生成器表达式、序列化与反序列化

    一.装饰器 一.装饰器的知识储备 1.可变长参数  :*args和**kwargs def index(name,age): print(name,age) def wrapper(*args,**k ...

  9. Python装饰器--decorator

    装饰器 装饰器实质是一个函数,其作用就是在不改动其它函数代码的情况下,增加一些功能.如果我们需要打印函数调用前后日志,可以这么做 def log(func): print('%s is running ...

  10. python中的装饰器decorator

    python中的装饰器 装饰器是为了解决以下描述的问题而产生的方法 我们在已有的函数代码的基础上,想要动态的为这个函数增加功能而又不改变原函数的代码 例如有三个函数: def f1(x): retur ...

随机推荐

  1. 扯什么kafka顺序消费,然后呢?古尔丹,代价是什么

    著名面试八股文之kafka为什么读写效率高,写的答案之一是partition顺序写,因而能保证分区内的不连续的有序性. 这里的重点是有序追加到磁盘,而不是严格意义上的完全有序性. 几年前参加了一大数据 ...

  2. 波场(Tron) 网页版钱包开源

    之前做区块链项目太难了,很多组件.工具没有开源项目,需要自己写很麻烦. 我整理了几个自己给公司开发项目的时候,分离出来的几个工具,已经上传到 Gihub 了,感觉浏览量还行,在这里给园子里的朋友分享下 ...

  3. 联想win8改win7

    知识点分析:目前联想出厂预装Windows 8的台式和一体机使用都是UEFI+GPT硬盘的组合,并且开启了安全启动,但是目前除Window 8以外的其他Windows系统均不支持这种模式,因此如果需要 ...

  4. 2020-08-31:描述HTTP的版本之间的区别,主要是1.0/1.1/2.0三个版本的区别。

    福哥答案2020-08-31: HTTP1.0与HTTP1.1的主要区别 1.长连接HTTP1.0:需要使用keep-alive参数来告知服务器端要建立一个长连接.HTTP1.1:默认支持长连接.2. ...

  5. Django接入drf_yasg2 API接口文档-完整操作(包含错误处理)

    drf_yasg2的简介: drf-yasg是Django RestFramework的一个扩展,使⽤drf_yasg2下载⾃动⽣成的api⽂档的json或yaml⽂件配置项. drf_yasg2的安 ...

  6. operation not supported on selected printer

    operation not supported on selected printer 版本原因 解决办法, 每次打印前先选择一次打印机属性,弹窗点确认还是取消都行,最后返回再点打印即可

  7. Djnago常用命令

    pip: bundle,创建包含多个包的pybundles:freeze,显示所有已安装的包:help,显示可用命令:install,安装包:search,搜索PyPi:uninstall,卸载包:u ...

  8. 天下苦 Spring 久矣,Solon v2.2.20 发布

    Solon 是什么框架? 一个,Java 新的生态型应用开发框架.它从零开始构建,有自己的标准规范与开放生态.与其他框架相比,它解决了两个重要的痛点:启动慢,费资源. 解决痛点? 由于Solon Be ...

  9. GitLib详细使用手册(windows系统)

    Git是一个开源的分布式版本控制系统,可以有效.高速地处理从很小到非常大的项目版本管理. 对gitlab的常见的使用有建立仓库.提交代码.更新代码.回滚代码.显示/修改日志.拉取分支.解决冲突.设置比 ...

  10. 大幅提升前端工作效率!Numeral.js数值格式化库来了!

    我们日常开发中,时常会碰到数值格式化操作的场景,今天了不起就为大家分享一款相对比较全面的数值格式化的JS库:Numeral.js Numeral.js Numeral.js 是一个用来对数值进行操作和 ...