@Autowiredprivate UserDetailsService userDetailsService;@Autowiredprivate AuthenticationManager authenticationManager;public void login(String username, String token) { /* UserDetails userDetails = org.springframework.security.core.userdetails.User .withUsername("user") .password("password") .passwordEncoder(s -> PasswordEncoderFactories.createDelegatingPasswordEncoder().encode(s)) .roles("USER") .build(); */ UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (userDetails != null) { // 这里可以进行一些其他的用户信息验证,例如验证token是否有效等 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); Authentication authentication = authenticationManager.authenticate(authenticationToken); SecurityContextHolder.getContext().setAuthentication(authentication); } else { // 用户不存在,进行一些其他处理,如创建新用户等 // do something }}
正常情况下,UserDetails
通过UserDetailsService.loadUserByUsername("")
获取,上述代码可用于:
1、模拟用户登录
2、接口调试时为用户授权
3、业务之外的虚拟授权
4、and so on...
/** * 转化前的节点 */public abstract class TreeSourceNode { // 获取节点ID public abstract String getId(); // 获取节点名称 public abstract String getName(); // 获取父节点ID public abstract String getParentId();}
import java.util.List;/** * 转化后的树形节点 */public class TreeDestNode<T extends TreeSourceNode> { // 默认展开的 level public static int SPREAD_LEVEL = 2; // 唯一标识 private String id; // 名称 private String name; // 级别 private int level; // 是否禁用,默认不禁用 private boolean disabled = false; // 是否默认选中 private boolean checked = true; // 是否默认展开 private boolean spread = false; // 子节点 private List<TreeDestNode<T>> children; // 元数据 private T meta; // ---------------------------------- setter public void setId(String id) { this.id = id; } public void setName(String name) { this.name = name; } public void setDisabled(boolean disabled) { this.disabled = disabled; } public void setChildren(List<TreeDestNode<T>> children) { this.children = children; } public void setMeta(T meta) { this.meta = meta; } public void setLevel(int level) { this.level = level; } public void setChecked(boolean checked) { this.checked = checked; } public void setSpread(boolean spread) { this.spread = spread; } // ---------------------------------- getter public T getMeta() { return meta; } public String getId() { return id; } public boolean isDisabled() { return disabled; } public List<TreeDestNode<T>> getChildren() { return children; } public int getLevel() { return level; } public boolean isChecked() { return checked; } public boolean isSpread() { return spread; } // name -> title public String getTitle() { return name; }}
import java.util.ArrayList;import java.util.List;/** * 树形数据工具类 */public class TreeHelper { private TreeHelper() { } public static <T extends TreeSourceNode> List<TreeDestNode<T>> convert(List<T> sourceNodeList) { List<TreeDestNode<T>> destNodeList = new ArrayList<TreeDestNode<T>>(); // 第一步,找出第一级的节点 // 1.1 统计所有节点的id List<String> allIds = new ArrayList<String>(); for (T sourceNode : sourceNodeList) { allIds.add(sourceNode.getId()); } // 所有父节点找不到对应的都是一级id for (T sourceNode : sourceNodeList) { if (!allIds.contains(sourceNode.getParentId())) { // 从每个一级节点,递归查找children TreeDestNode<T> destNode = new TreeDestNode<T>(); destNode.setId(sourceNode.getId()); destNode.setName(sourceNode.getName()); destNode.setLevel(1); destNode.setMeta(sourceNode); if (TreeDestNode.SPREAD_LEVEL >= destNode.getLevel()) { destNode.setSpread(true); } List<TreeDestNode<T>> myChilds = getChilderen(sourceNodeList, destNode); destNode.setChildren(myChilds.isEmpty() ? null : myChilds); destNodeList.add(destNode); } } return destNodeList; } // 递归获取子节点 private static <T extends TreeSourceNode> List<TreeDestNode<T>> getChilderen(List<T> sourceNodeList, TreeDestNode<T> parentNode) { List<TreeDestNode<T>> childrenList = new ArrayList<TreeDestNode<T>>(); for (T sourceNode : sourceNodeList) { if (sourceNode.getParentId().equals(parentNode.getId())) { TreeDestNode<T> children = new TreeDestNode<T>(); children.setId(sourceNode.getId()); children.setName(sourceNode.getName()); children.setLevel(parentNode.getLevel() + 1); children.setMeta(sourceNode); if (TreeDestNode.SPREAD_LEVEL >= children.getLevel()) { children.setSpread(true); } List<TreeDestNode<T>> myChilds = getChilderen(sourceNodeList, children); children.setChildren(myChilds.isEmpty() ? null : myChilds); childrenList.add(children); } } return childrenList; }}
用法:
1、新建类,extends TreeSourceNode
2、设置 id、name、parentId
3、调用List<TreeDestNode<VO>> destNodeList = TreeHelper.convert(VOs);
@Slf4j@Componentpublic class TestRunner implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception { log.info("[boot] 程序启动到最后会执行此代码,可以注入 Bean"); }}
或 2
@Slf4j@Componentpublic class TestRunner implements CommandLineRunner { @Override public void run(String... args) throws Exception { log.info("[boot] 程序启动到最后会执行此代码,可以注入 Bean"); }}
或 3
// 写在 main 方法中 run() 的前后public static void main(String[] args) { // do something SpringApplication.run(Application.class, args); // do something}
// 此代码将在tomcat等容器初始化完成后,启动web应用时最先执行public class TestInit implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) throws ServletException { // 此方法等同于web.xml配置文件,即xml文件的代码化实现 // do something }}
或
@Componentpublic class TestRunner { @PostConstruct public void runner() { // 此注解有很多的使用限制,不一定保证会执行,具体可查看API说明 }}
// 此代码将在上下文初始化和销毁过程中执行,不在最前和最后,相对靠后public class InstallListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { // todo } @Override public void contextDestroyed(ServletContextEvent sce) { // todo }}
上述代码还需配合配置项使用
<!-- web.xml --><listener><listener-class>com.example.demo.InstallListener</listener-class></listener>
或
<!-- web.xml --><servlet><servlet-name>ConnectorServlet</servlet-name><servlet-class>net.fckeditor.connector.ConnectorServlet</servlet-class><load-on-startup>1</load-on-startup></servlet>
]]>本站是镜像站点,主要探讨Java相关技术,所有文章都有在主站转载
本文永久链接:https://www.xinac.net/9257.html
config
下Project/config/application.properties# 配置文件可以在项目jar包之外./app.jar./config/application.properties
Project/application.properties# 配置文件可以在项目jar包之外./app.jar./application.properties
resource
目录的config
目录下Project/src/main/resources/config/application.properties
resource
目录下Project/src/main/resources/application.properties
]]>相同名称的两个属性,将会加载高优先级配置文件中的属性对应的值。
服务器配置
2核4G内存5M带宽的华为云服务器。内存使用率80%左右,5M带宽用于一般的访问足够了,也总是有别有用心的非法访问导致服务器卡死。
本站架构
本站使用了Halo博客系统,后台Java开发,主题使用Freemarker模板开发的。数据库用的MySQL,Web服务器是nginx做的反代。
Halo博客
目前使用的是最新的v1.4.2版本
镜像下载地址:https://halo.cary.tech
主题
主题是Fantastic主题修改版
主题开源地址:https://github.com/jinqilin721/halo-theme-xinac-fantastic
CDN & 全站加速
本站的图片等附件使用了CDN加速,主要也是阿里云的CDN。本站同时使用了阿里云的全站加速功能。
关于全站加速,据说可以提高网站的访问速度,但是问题多多,一旦配置错误网站无法访问,且有些页面或网址用了会有问题。慎用。
关于CDN,主要加速静态文件的访问速度。阿里云的没什么问题,访问稳定;七牛云每月免费10G流量,但仅限HTTP访问,HTTPS的话收费比阿里云要高一点点。
API & 友链页面
友链页面的网址LOGO都是自动获取的,使用了本站提供的API。
新版友链页面是修改的WebStack的版本。
Favicon图标自动获取API:https://api.xinac.net/icon/?url=www.xinac.cn
WebStack网址导航:https://github.com/xinac721/WebStack-xinac
随机一言API:https://api.xinac.net/words
爬虫
一般的爬虫本站是默许的,但总是有一些没有技术含量的爬虫、请求、测试等行为,本站屏蔽了一些自动化的工具。本站内容有限,技术有限,望正确使用。
爆破 & 攻击
最近针对服务器的爆破和攻击行为越来越多,防不胜防,希望能将技术用于正途吧。
模板(Template Method)模式:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
模板方法(Template Method)模式的定义如下:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式。
在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。
创建模板抽象类,定义公共方法、抽象方法和模板方法
public abstract class Template { /** * 办理业务 */ abstract void handle(); /** * 评价 */ abstract void evaluation(); /** * 取号 */ public void takeNumber() { System.out.println("取号成功,请排队等候。"); } /** * 排队 */ public void lineUp() { System.out.println("排队中,等待叫号..."); } /** * 模板方法 */ public final void process() { takeNumber(); lineUp(); handle(); evaluation(); }}
创建实现业务类,实现具体的业务和评价
/** * 开户业务 */public class OpenAccount extends Template { @Override void handle() { System.out.println("办理业务中:开户业务"); } @Override void evaluation() { System.out.println("你的评价是:优"); }}
创建实现业务类,实现具体的业务和评价
/** * 存款业务 */public class Deposit extends Template { @Override void handle() { System.out.println("办理业务中:存款业务"); } @Override void evaluation() { System.out.println("你的评价是:好"); }}
public class Test { public static void main(String[] args) { Template template = new OpenAccount(); template.process(); System.out.println(); template = new Deposit(); template.process(); }}
测试输出内容
取号成功,请排队等候。排队中,等待叫号...办理业务中:开户业务你的评价是:优取号成功,请排队等候。排队中,等待叫号...办理业务中:存款业务你的评价是:好
注意事项
为防止恶意操作,一般模板方法都加上 final 关键词。
优缺点
1、封装不变部分,扩展可变部分
2、提取公共代码,便于维护
3、行为由父类控制,子类实现
4、每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大
5、反向的控制结构,提高了代码阅读的难度
模式结构
1、抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。
2、具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。
参考文章
1、http://c.biancheng.net/view/1376.html
2、https://www.runoob.com/design-pattern/template-pattern.html
组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
组合(Composite)模式的定义:有时又叫作部分-整体模式,它是一种将对象组合成树状的层次结构的模式,用来表示“部分-整体”的关系,使用户对单个对象和组合对象具有一致的访问性。
组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。
这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。
创建组合结构类,定义子节点和add()、remove()、get()方法
public class University { private String name; private List<University> colleges; public University(String name) { this.name = name; colleges = new ArrayList<University>(); } public void add(University university) { colleges.add(university); } public void remove(University university) { colleges.remove(university); } public List<University> getColleges() { return colleges; } @Override public String toString() { return "NAME: -> " + name; }}
public class Test { public static void main(String[] args) { University university = new University("XXX大学"); University c1 = new University("物理学院"); University c11 = new University("大学物理1"); University c12 = new University("大学物理2"); University c2 = new University("化学学院"); University c21 = new University("大学化学1"); University c22 = new University("大学化学2"); university.add(c1); university.add(c2); c1.add(c11); c1.add(c12); c2.add(c21); c2.add(c22); System.out.println(university); System.out.println(); for (University university2 : university.getColleges()) { System.out.println(university2); for (University university3 : university2.getColleges()) { System.out.println(university3); } System.out.println(); } }}
测试输出内容
NAME: -> XXX大学NAME: -> 物理学院NAME: -> 大学物理1NAME: -> 大学物理2NAME: -> 化学学院NAME: -> 大学化学1NAME: -> 大学化学2
优缺点
1、高层模块调用简单。
2、节点自由增加。
3、在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。
4、设计较复杂,客户端需要花更多时间理清类之间的层次关系。
5、不容易限制容器中的构件。
6、不容易用继承的方法来增加构件的新功能。
模式结构
1、抽象构件(Component)角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。
2、树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于实现抽象构件角色中声明的公共接口。
3、树枝构件(Composite)角色:是组合中的分支节点对象,它有子节点。它实现了抽象构件角色中声明的接口,它的主要作用是存储和管理子部件,通常包含 add()、remove()、get() 等方法。
参考文章
1、http://c.biancheng.net/view/1373.html
2、https://www.runoob.com/design-pattern/composite-pattern.html
享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。
享元(Flyweight)模式的定义:运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。
享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。
享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。
在面向对象程序设计过程中,有时会面临要创建大量相同或相似对象实例的问题。创建那么多的对象将会耗费很多的系统资源,它是系统性能提高的一个瓶颈。这些对象有很多相似的地方,如果能把它们相同的部分提取出来共享,则能节省大量的系统资源,这就是享元模式的产生背景。
public interface Shape { void draw();}
public class Circle implements Shape { private String color; private int x; private int y; private int radius; public Circle(String color) { super(); this.color = color; } public void setColor(String color) { this.color = color; } public void setX(int x) { this.x = x; } public void setY(int y) { this.y = y; } public void setRadius(int radius) { this.radius = radius; } @Override public void draw() { System.out.println("绘制圆形-> 长:" + x + ",宽:" + y + ",半径:" + radius + ",颜色:" + color); }}
public class Factory { private static final Map<String, Circle> circleMap = new HashMap<String, Circle>(); public static Circle getCircle(String color) { Circle circle = circleMap.get(color); if (circle == null) { circle = new Circle(color); circleMap.put(color, circle); System.out.println("-> 绘制 " + color + " 颜色的圆形"); } return circle; }}
public class Test { private static final String colors[] = { "Red", "Green", "Blue", "White", "Black" }; public static void main(String[] args) { for (int i = 0; i < 10; ++i) { Circle circle = Factory.getCircle(getRandomColor()); circle.setX(getRandomX()); circle.setY(getRandomY()); circle.setRadius(100); circle.draw(); } } private static String getRandomColor() { return colors[(int) (Math.random() * colors.length)]; } private static int getRandomX() { return (int) (Math.random() * 100); } private static int getRandomY() { return (int) (Math.random() * 100); }}
测试输出内容
-> 绘制 White 颜色的圆形绘制圆形-> 长:9,宽:52,半径:100,颜色:White-> 绘制 Red 颜色的圆形绘制圆形-> 长:83,宽:30,半径:100,颜色:Red-> 绘制 Green 颜色的圆形绘制圆形-> 长:37,宽:32,半径:100,颜色:Green-> 绘制 Black 颜色的圆形绘制圆形-> 长:68,宽:41,半径:100,颜色:Black绘制圆形-> 长:3,宽:72,半径:100,颜色:Black绘制圆形-> 长:99,宽:46,半径:100,颜色:Black-> 绘制 Blue 颜色的圆形绘制圆形-> 长:64,宽:24,半径:100,颜色:Blue绘制圆形-> 长:90,宽:76,半径:100,颜色:Blue绘制圆形-> 长:60,宽:54,半径:100,颜色:Green绘制圆形-> 长:72,宽:27,半径:100,颜色:Green
注意事项
1、注意划分外部状态和内部状态,否则可能会引起线程安全问题。
2、这些类必须有一个工厂对象加以控制。
3、享元模式中存在以下两种状态:
优缺点
1、大大减少对象的创建,降低系统的内存,使效率提高。
2、为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。
3、读取享元模式的外部状态会使得运行时间稍微变长。
模式结构
1、抽象享元角色(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
2、具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。
3、非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
4、享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
参考文章
1、http://c.biancheng.net/view/1371.html
2、https://www.runoob.com/design-pattern/flyweight-pattern.html
外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
外观(Facade)模式的定义:是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体的细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。
外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。
这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。
软件设计也是这样,当一个系统的功能越来越强,子系统会越来越多,客户对系统的访问也变得越来越复杂。这时如果系统内部发生改变,客户端也要跟着改变,这违背了“开闭原则”,也违背了“迪米特法则”,所以有必要为多个子系统提供一个统一的接口,从而降低系统的耦合度,这就是外观模式的目标。
创建Computer
接口类,定义start
方法
public interface Computer { void start();}
实现Computer
接口,CPU开始工作
public class Cpu implements Computer { @Override public void start() { System.out.println("start cpu."); }}
实现Computer
接口,Memory开始工作
public class Memory implements Computer { @Override public void start() { System.out.println("start memory."); }}
实现Computer
接口,Disk开始工作
public class Disk implements Computer { @Override public void start() { System.out.println("start disk."); }}
创建外观类,封装计算机启动需要的设备,用于对外展现
public class FacadeDemo { private Cpu cpu; private Memory memory; private Disk disk; public FacadeDemo() { cpu = new Cpu(); memory = new Memory(); disk = new Disk(); } public void startCpu() { cpu.start(); } public void startMemory() { memory.start(); } public void startDisk() { disk.start(); }}
测试外观模式的实现过程
public class Test { public static void main(String[] args) { FacadeDemo demo = new FacadeDemo(); demo.startCpu(); demo.startMemory(); demo.startDisk(); }}
测试输出内容
start cpu.start memory.start disk.
优缺点
1、减少系统相互依赖。
2、提高灵活性。
3、提高了安全性。
4、不能很好地限制客户使用子系统类。
5、增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
模式结构
1、外观(Facade)角色:为多个子系统对外提供一个共同的接口。
2、子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
3、客户(Client)角色:通过一个外观角色访问各个子系统的功能。
参考文章
1、http://c.biancheng.net/view/1369.html
2、https://www.runoob.com/design-pattern/facade-pattern.html
装饰器(Decorator)模式:动态的给对象增加一些职责,即增加其额外的功能。
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
在现实生活中,常常需要对现有产品增加新的功能或美化其外观,如房子装修、相片加相框等。在软件开发过程中,有时想用一些现存的组件。这些组件可能只是完成了一些核心功能。但在不改变其结构的情况下,可以动态地扩展其功能。所有这些都可以釆用装饰模式来实现。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
通常情况下,扩展一个类的功能会使用继承方式来实现。但继承具有静态特征,耦合度高,并且随着扩展功能的增多,子类会很膨胀。如果使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象,并在保持真实对象的类结构不变的前提下,为其提供额外的功能,这就是装饰模式的目标。
创建People
接口类,定义init
方法
public interface People { void init();}
创建接口实现类,初始化一个战士
public class Hero implements People { @Override public void init() { System.out.println("初始化战士,无技能!"); }}
创建装饰器抽象类,并实现People
接口类
public abstract class SkillDecorator implements People { private People people; public SkillDecorator(People people) { this.people = people; } @Override public void init() { people.init(); }}
创建抽象类具体实现类,用于扩展People
类的功能
public class WanJianSkill extends SkillDecorator { public WanJianSkill(People people) { super(people); } @Override public void init() { super.init(); setSkill(); } private void setSkill() { System.out.println("获得技能:万箭齐发!"); }}
创建抽象类具体实现类,用于扩展People
类的功能
public class WuLeiSkill extends SkillDecorator { public WuLeiSkill(People people) { super(people); } @Override public void init() { super.init(); setSkill(); } private void setSkill() { System.out.println("获得技能:五雷轰顶!"); }}
测试装饰器模式的实现,分别实现原始功能和扩展后的功能
public class Test { public static void main(String[] args) { People people = new Hero(); people.init(); System.out.println(); SkillDecorator skill1 = new WanJianSkill(people); skill1.init(); System.out.println(); SkillDecorator skill2 = new WuLeiSkill(people); skill2.init(); System.out.println(); SkillDecorator skill3 = new WuLeiSkill(skill1); skill3.init(); }}
测试输出内容
初始化战士,无技能!初始化战士,无技能!获得技能:万箭齐发!初始化战士,无技能!获得技能:五雷轰顶!初始化战士,无技能!获得技能:万箭齐发!获得技能:五雷轰顶!
注意事项
可代替继承。
优缺点
1、采用装饰模式扩展对象的功能比采用继承方式更加灵活。
2、可以设计出多个不同的具体装饰类,创造出多个不同行为的组合。
3、装饰类和被装饰类可以独立发展,不会相互耦合。
4、装饰模式是继承的一个替代模式。
5、装饰模式可以动态扩展一个实现类的功能。
6、装饰模式增加了许多子类,如果过度使用会使程序变得很复杂。
模式结构
1、抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。
2、具体构件(Concrete Component)角色:实现抽象构件,通过装饰角色为其添加一些职责。
3、抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
4、具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
参考文章
1、http://c.biancheng.net/view/1366.html
2、https://www.runoob.com/design-pattern/decorator-pattern.html