大家好,我是一位在java学习圈中不愿意透露姓名并苟且偷生的小学员,如果文章有错误之处,还望海涵,欢迎多多指正
如果你从本文 get 到有用的干货知识,请帮忙点个赞呗,据说点赞的都拿到了offer
模板方法模式
简介:
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。
这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装,并且能在原有功能的基础上实现不同装饰功能的混合。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
我们通过下面的实例来演示装饰器模式的用法。比如,我们将把一个形状装饰上不同的颜色,同时又不改变形状类。
又比如,手抓饼,基础是饼,然后是添加装饰(比如菜叶,香肠,肉片)
那么上面提到的装饰混合就来了,我可以实现一个 饼+菜叶,也可以实现一个饼加香肠,或者饼+香肠+肉片
如果对高中学的排列组合还有印象,那么这个装饰混合就是这种情况
只不过每种排列组合后的结果必定至少包含一个原始的基础“饼”
引申案例
财务为销售部的员工计算每月应得的奖金,员工奖金分三类(经理也是员工,害,在牛也是打工人啊)
- 默认奖金(无论销售额多少):销售额 * 5%
- 销售额超过7000的员工,奖金为:第一种的基础上加2%
- 销售经理的奖金为:自己作为员工的奖金 + 销售部全体人员(包括自己)的销售额 * 1%
案例分析
案例中出现的对象:财务(用于计算奖金的)
另外员工信息单独存储在一个类中,这个类充当数据库(模拟一个查询的过程,当然这都不是重点好吧,我们先写出这个案例在去发现有什么不妥的地方需要优化,然后才是引出重头戏装饰器模式)
案例代码
员工类
package decorator.nopattern;
/**
* @author xingyu
* @date 2021/9/8 9:09
* 员工
*/
public class Staff {
private Long id;
//销售额
private Double saleroom;
//员工身份:假设“普通”表示员工,“经理”表示销售部经理(就两种身份)
private String identity;
public Staff(Long id, Double saleroom, String identity) {
this.id = id;
this.saleroom = saleroom;
this.identity = identity;
}
public Long getId() {
return id;
}
public Double getSaleroom() {
return saleroom;
}
public String getIdentity() {
return identity;
}
}
StaffDatabase类充当数据库
package decorator.nopattern;
import java.util.HashMap;
import java.util.Map;
/**
* @author xingyu
* @date 2021/9/8 9:07
* StaffDatabase(员工数据库)
* 此类模拟数据库
* 从数据库中查询数据
*/
public class StaffDatabase {
/**
* Map表示员工表
* 使用long防止员工数量过多出现int不够用的情况
* 这是数据库中的知识了,不再赘述。理解一下这个类的作用即可
* 不理解也没关系,接着往下看即可
*/
private static Map<Long,Staff> staffMap;
/**
* 初始化数据
*/
static {
staffMap = new HashMap<>();
staffMap.put(1L,new Staff(1L, 6000.0, "普通"));
staffMap.put(2L,new Staff(2L, 8000.0, "普通"));
staffMap.put(3L,new Staff(3L, 10000.0, "经理"));
}
/**
* 根据员工id查询员工信息
* @param id
* @return
*/
public static Staff selectOne(Long id){
return staffMap.get(id);
}
/**
* 查询所有员工的信息
* @return
*/
public static Map<Long,Staff> selectAll(){
return staffMap;
}
}
财务类(用于计算员工奖金)
因为计算奖金的方式有三种,因此根据单一职责的原则(简单理解为一个类做自己的事情,一个方法做自己的事情),分出三个方法去做自己的事情
package decorator.nopattern;
import decorator.withpattern.Rule;
import java.util.Map;
/**
* @author xingyu
* @date 2021/9/8 9:01
* Finance(财务)
* 负责计算每个员工的工资
* 原始类(为什么要叫原始类后续会提到)
*/
public class Finance {
/**
* 根据员工的编号(主键-数据库中的术语,表示唯一性)计算该员工的奖金
*/
public double calculateBonus(Long id){
double bonus = 0;
bonus += calculateOne(id);
bonus += calculateTwo(id);
bonus += calculateThree(id);
System.out.println("id为 " + id + " 的员工总奖金为:" + bonus);
return bonus;
}
/**
* 计算第一类的奖金[5000,7000)
*/
private double calculateOne(Long id){
double res = 0;
Staff staff = StaffDatabase.selectOne(id);
double baseSalary = staff.getSaleroom();
res = baseSalary * 0.05;
System.out.println("id为 " + id + " 的员工的第一类奖金为:" + res);
return res;
}
/**
* 计算第二类的奖金(销售额大于)
*/
private double calculateTwo(Long id){
double res = 0;
Staff staff = StaffDatabase.selectOne(id);
double baseSalary = staff.getSaleroom();
if(baseSalary >= 7000){
res = baseSalary * 0.02;
}
System.out.println("id为 " + id + " 的员工的第二类奖金为:" + res);
return res;
}
/**
* 计算经理的除个人奖金外的奖金
*/
private double calculateThree(Long id){
double res = 0;
Staff staff = StaffDatabase.selectOne(id);
if("经理".equals(staff.getIdentity())){
double sum = 0;
Map<Long,Staff> staffMap = StaffDatabase.selectAll();
for (Long curId : staffMap.keySet()){
Staff curStaff = StaffDatabase.selectOne(curId);
sum += curStaff.getSaleroom();
}
res = sum * 0.01;
}
System.out.println("id为 " + id + " 的经理的除个人奖金外的奖金为:" + res);
return res;
}
}
主方法测试
package decorator;
import decorator.nopattern.Finance;
import decorator.withpattern.Decorator;
import decorator.withpattern.DecoratorOne;
import decorator.withpattern.DecoratorThree;
import decorator.withpattern.DecoratorTwo;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
/**
* @author xingyu
* @date 2021/9/7 20:34
*/
@SuppressWarnings("all")
public class DecoratorTest {
public static void main(String[] args) {
//创建财务对象
Finance finance = new Finance();
//让财务计算 id 为 3的员工的奖金
finance.calculateBonus(3L);
}
}
执行结果
分析案例代码有什么不妥之处
思考有什么不好的地方?
扩展性不好,因为可能奖金的计算方式如果修改就会造成修改源代码的问题
还有就是添加新的奖金计算方式。如第三类:如果销售额[5000,7000)的员工奖金为 销售额*3%
或者是删除某种奖金计算方式,也要改动源代码,违背了设计的开闭原则
所以现在急需解决的问题是:如何动态的计算一个员工的奖金?
由此引申出装饰器模式
创建装饰器类,每一个装饰器类负责一种奖金的计算,而此计算是建立在前面的奖金的基础上进行累加
如何做到在之前计算的奖金的基础上进行累加呢?
在该装饰器类中添加上一个计算奖金的装饰器对象作为属性
举例:
原始计算是的奖金是 0,第一次累加假设装饰器one选取第一类奖金计算方式,那么该类添加计算原始类的对象(也就是一开我们说的财务类,此时财务计算奖金的方法就发生变化了)作为属性,先将 0 计算出来,然后计算第一类奖金的结果和 0 累加
用装饰器模式优化案例代码
首先作为模拟的数据库StaffDatabase类无需改动
装饰器中能装财务类的对象作为属性,而装饰器又能装别的装饰器对象作为属性
因为装饰要实现混合效果(加啥装饰都行),所以某个装饰器中装的无论是财务类对象还是别的装饰器对象,作为属性其类型都不能写死,而是通过规则实现多态,这样就能实现混合效果了
Rule(*的规则,被装饰者和装饰者都需要遵循)
财务类(原始类)中是不会让别的装饰器对象充当属性的,并且他自己也不是一个装饰器,因为他是被装饰器装饰的,就好比之前提到的手抓饼中的“饼”
如果看不懂为什么需要这个规则,那么看到后续的代码可能你就豁然开朗了
package decorator.withpattern;
/**
* @author xingyu
* @date 2021/9/8 10:19
* 统一原始类与装饰器的方法名,同时让第一个装饰器能添加原始类的对象作为属性
* 还能混淆用户的视野,分不清具体调用的是哪个类的方法
*/
public abstract class Rule {
public abstract double calculateBonus(Long id);
}
装饰器规则
为了实现混合的效果,装饰器类中能让不同的装饰器类的对象充当属性,而不是一个写死的类型
package decorator.withpattern;
/**
* @author xingyu
* @date 2021/9/8 10:11
* 装饰器规则
*/
public abstract class Decorator extends Rule {
/**
* 前一个装饰器对象需要让当前类对象使用(就是这个对象需要被子类使用),所以用protected
*/
protected Rule rule;
public Decorator(Rule rule) {
this.rule = rule;
}
/**
* 计算此时的奖金 = 当前奖金 + 之前的奖金
* @return
*/
public abstract double calculateBonus(Long id);
}
第一类奖金的计算类(装饰器)
package decorator.withpattern;
import decorator.nopattern.Staff;
import decorator.nopattern.StaffDatabase;
/**
* @author xingyu
* @date 2021/9/8 10:14
* 此装饰器用于计算第一类的奖金(默认奖金(无论销售额多少):销售额 * 5%)
*/
public class DecoratorOne extends Decorator {
public DecoratorOne(Rule rule) {
super(rule);
}
@Override
public double calculateBonus(Long id) {
//先调用前一个装饰器(也可能不是装饰器而是原始类)的装饰方法
double preBonus = rule.calculateBonus(id);
double res = 0;
Staff staff = StaffDatabase.selectOne(id);
double baseSalary = staff.getSaleroom();
res = baseSalary * 0.05;
System.out.println("添加 " + this.getClass().getSimpleName() + " 装饰器后的奖金为:" + (preBonus + res));
return preBonus + res;
}
}
第二类奖金的计算类(装饰器)
package decorator.withpattern;
import decorator.nopattern.Staff;
import decorator.nopattern.StaffDatabase;
/**
* @author xingyu
* @date 2021/9/8 10:34
* 此装饰器用于计算第二类的奖金(销售额超过7000的员工,奖金为:第一种的基础上加2%)
*/
public class DecoratorTwo extends Decorator {
public DecoratorTwo(Rule rule) {
super(rule);
}
@Override
public double calculateBonus(Long id) {
//先调用前一个装饰器(也可能不是装饰器而是原始类)的装饰方法
double preBonus = rule.calculateBonus(id);
double res = 0;
Staff staff = StaffDatabase.selectOne(id);
double baseSalary = staff.getSaleroom();
if(baseSalary >= 7000){
res = baseSalary * 0.02;
}
System.out.println("添加 " + this.getClass().getSimpleName() + " 装饰器后的奖金为:" + (preBonus + res));
return preBonus + res;
}
}
第三类奖金的计算类(装饰器)
package decorator.withpattern;
import decorator.nopattern.Staff;
import decorator.nopattern.StaffDatabase;
import java.util.Map;
/**
* @author xingyu
* @date 2021/9/8 10:36
* 计算经理的除个人奖金外的奖金(销售部全体人员(包括自己)的销售额 * 1%)
*/
public class DecoratorThree extends Decorator {
public DecoratorThree(Rule rule) {
super(rule);
}
@Override
public double calculateBonus(Long id) {
//先调用前一个装饰器(也可能不是装饰器而是原始类)的装饰方法
double preBonus = rule.calculateBonus(id);
double res = 0;
Staff staff = StaffDatabase.selectOne(id);
if("经理".equals(staff.getIdentity())){
double sum = 0;
Map<Long,Staff> staffMap = StaffDatabase.selectAll();
for (Long curId : staffMap.keySet()){
Staff curStaff = StaffDatabase.selectOne(curId);
sum += curStaff.getSaleroom();
}
res = sum * 0.01;
}
System.out.println("添加 " + this.getClass().getSimpleName() + " 装饰器后的奖金为:" + (preBonus + res));
return preBonus + res;
}
}
Finance 财务类(原始类并非装饰器)
此时的财务类继承Rule规则,表示原始类的对象能传入其他装饰器中作为属性,此时是不是更清楚Rule规则的作用呢
package decorator.nopattern;
import decorator.withpattern.Rule;
import java.util.Map;
/**
* @author xingyu
* @date 2021/9/8 9:01
* Finance(财务)
* 负责计算每个员工的工资
* 原始类(为什么要叫原始类后续会提到)
*/
public class Finance extends Rule {
/**
* 基础的奖金为0
* 后续想要添加别的计算方式只要将此类的对象作为那个装饰器的属性
* 然后在正式计算之前先调用此对象的calculateBonus方法计算出之前的奖金
* 有点像递归的执行过程,一层套一层
* @param id
* @return
*/
@Override
public double calculateBonus(Long id) {
double firstBonus = 0;
System.out.println("原始奖金为:" + firstBonus);
return firstBonus;
}
}
主方法测试
package decorator;
import decorator.nopattern.Finance;
import decorator.withpattern.Decorator;
import decorator.withpattern.DecoratorOne;
import decorator.withpattern.DecoratorThree;
import decorator.withpattern.DecoratorTwo;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
/**
* @author xingyu
* @date 2021/9/7 20:34
* 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。
* 这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装,并且能在原有功能的基础上实现不同装饰功能的混合。
*
* 这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
* 我们通过下面的实例来演示装饰器模式的用法。比如,我们将把一个形状装饰上不同的颜色,同时又不改变形状类。
* 又比如,手抓饼,基础是饼,然后是添加装饰(比如菜叶,香肠,肉片)
* 那么上面提到的装饰混合就来了,我可以实现一个 饼+菜叶,也可以实现一个饼加香肠,或者饼+香肠+肉片
* 如果对高中学的排列组合还有印象,那么这个装饰混合就是这种情况
* 只不过每种排列组合后的结果必定至少包含一个原始的基础“饼”
*
* 案例:
* 财务给销售部的员工发工资(基础工资5000),员工奖金分三类
* 1.默认奖金(无论销售额多少):销售额 * 5%
* 2.销售额超过7000的员工,奖金为:第一种的基础上加2%
* 3.销售经理的奖金为:自己作为员工的奖金 + 销售部全体人员(包括自己)的销售额 * 1%
*/
@SuppressWarnings("all")
public class DecoratorTest {
public static void main(String[] args) {
//原始类对象是基础必要要有,其他装饰在此基础上进行装饰
Finance finance = new Finance();
//第一种情况乱序(先算员工的第二类奖金应该是多少,然后算第一类奖金和第二类奖金的和是多少
//最后算该员工可能是经理的情况其总奖金是多少)
// Decorator decoratorTwo = new DecoratorTwo(finance);
// Decorator decoratorOne = new DecoratorOne(decoratorTwo);
// Decorator decoratorThree = new DecoratorThree(decoratorOne);
//第二种情况正常顺序
// Decorator decoratorOne = new DecoratorOne(finance);
// Decorator decoratorTwo = new DecoratorTwo(decoratorOne);
// Decorator decoratorThree = new DecoratorThree(decoratorTwo);
//第三种情况删除一种计算方式(假设公司取消了第一类奖金)
Decorator decoratorTwo = new DecoratorTwo(finance);
Decorator decoratorThree = new DecoratorThree(decoratorTwo);
decoratorThree.calculateBonus(3L);
//引申:Java中的I/O就用到了装饰器模式,看到这个形式是不是很熟悉呢
// File file = new File("");
// FileInputStream fis = new FileInputStream(file);
// BufferedInputStream bis = new BufferedInputStream(fis);
}
}
执行结果展示
装饰器模式总结:
意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
何时使用:在不想增加很多子类的情况下扩展类的功能。
如何解决:将具体功能职责划分,同时使用装饰者模式。
优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
缺点:多层装饰比较复杂。
使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销功能。
注意事项:可代替继承。
觉得文章不错的小伙伴在线求一键三连呀
看完了的小伙伴们是不是对装饰器模式有了更深刻的理解呢?希望这篇文章能帮到小伙伴们更好的理解装饰器模式的设计思想
之后开始慢慢的更新每一种设计模式,通过生动形象的生活现象举例带你感受设计模式的世界,其实设计模式不难,只是当我们面对某个场景时想不到用哪个设计模式该不该用设计模式,怎样用才更合理…
博客内容来自腾讯课堂渡一教育拓哥,以及自己的一些理解认识,同时看了其他大牛写的设计模式技术文章综合总结出来的