Java开发设计模式 09:装饰器模式

Posted 尹煜

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java开发设计模式 09:装饰器模式相关的知识,希望对你有一定的参考价值。

1 装饰器模式介绍

装饰器模式是一种结构型设计模式,它允许你在运行时通过将对象包装在装饰器类的对象中来扩展一个对象的功能。

装饰器模式可以动态地为对象添加新的功能,而无需修改原始对象代码。这种模式通过创建一系列包装器来实现递归地嵌套对象来扩展功能。每个包装器都通过增加一些新的行为或修改原始对象的行为来扩展对象的功能。

📌 与代理模式区别

  • 装饰器模式与前面介绍的代理模式在结构上是一样的。对装饰器模式来说,装饰者和被装饰者都实现同一个接口或抽象类;对代理模式来说,代理类和被代理的类都实现同一个接口或抽象类,在结构上确实没有区别。
  • 但是它们的作用不同装饰器模式强调的是增强自身,在被装饰之后你能够在被增强的类上使用增强后的功能,增强后你还是你,只是被强化了;代理模式强调要让别人帮你去做事情,以及添加一些与你本身的业务没有太多关系的事情(记录日志、设置缓存等),重点在于让别人帮你做。

📌 优缺点

  • 优点:动态地扩展对象的功能,避免了使用继承时子类的数量庞大、继承结构复杂的问题,同时保持类结构简单清晰。
  • 缺点:可能产生过多的小对象,过多的装饰器会使程序变得复杂难懂。

2 装饰器模式实现

以用户服务的查询为例:

📌 1.定义用户服务

/**
 * 用户服务
 */
public interface UserService 
    /**
     * 查询
     */
    public void query();

📌 2.实现用户服务

/**
 * 用户服务实现类
 */
public class UserServiceImpl implements UserService 
    @Override
    public void query() 
        System.out.println("查询了一个用户");
    

📌 3.定义装饰器

/**
 * 装饰器
 */
public class Decorator implements UserService
    /**
     * 用户服务<br>
     * 定义为 protected 是为了方便子类的后置处理器使用
     */
    protected UserService userService;
    
    /**
     * 装饰用户服务
     * @param userService 用户服务
     */
    public Decorator(UserService userService) 
        this.userService = userService;
    
    
    @Override
    public void query() 
        userService.query();
    

📌 4.定义装饰器后置处理器

/**
 * 装饰器后置处理器
 */
public class DecoratorPostProcessor extends Decorator
    /**
     * 使用父类的装饰器装饰用户服务
     * @param userService 用户服务
     */
    public DecoratorPostProcessor(UserService userService) 
        super(userService);
    
    
    @Override
    public void query() 
        userService.query();
        // 后置处理
        System.out.println("查找字典表给性别赋值");
    

📌 5.调用

// 装饰用户服务实现类(注意,这里需要用装饰器后置处理器装饰,因为后置处理的业务逻辑写在里面)
Decorator decorator = new DecoratorPostProcessor(new UserServiceImpl());
// 调用装饰后的查询方法
decorator.query();

控制台输出结果:

📌 6.嵌套使用

在开发场景中,有时候一次后置处理并不能得到预期的数据(比如数字转化为编码,编码再转化为名称),需要再次处理,此时再去定义一个装饰器是没有必要的,可以嵌套使用。

// 嵌套使用
DecoratorPostProcessor decorator1 = new DecoratorPostProcessor(decorator);
decorator1.query();

执行main方法:

跟我学Java设计模式第7天:行为型设计模式

Java设计模式文章目录

跟我学Java设计模式第一天:设计模式概述和软件设计原则

跟我学Java设计模式第二天:简单工厂模式、工厂方法模式、抽象工厂模式

跟我学Java设计模式第三天:代理模式、适配器、装饰者等其中模式结构

跟我学Java设计模式第4天:结构型模式大全

跟我学Java设计模式第5天:行为型设计模式汇总

跟我学Java设计模式第6天:行为型设计模式汇总源码



6,行为型模式

6.11 解释器模式

6.11.1 概述

如上图,设计一个软件用来进行加减计算。我们第一想法就是使用工具类,提供对应的加法和减法的工具方法。

//用于两个整数相加
public static int add(int a,int b)
    return a + b;


//用于两个整数相加
public static int add(int a,int b,int c)
    return a + b + c;


//用于n个整数相加
public static int add(Integer ... arr) 
    int sum = 0;
    for (Integer i : arr) 
        sum += i;
    
    return sum;

上面的形式比较单一、有限,如果形式变化非常多,这就不符合要求,因为加法和减法运算,两个运算符与数值可以有无限种组合方式。比如 1+2+3+4+5、1+2+3-4等等。

显然,现在需要一种翻译识别机器,能够解析由数字以及 + - 符号构成的合法的运算序列。如果把运算符和数字都看作节点的话,能够逐个节点的进行读取解析运算,这就是解释器模式的思维。

定义:

给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。

在解释器模式中,我们需要将待解决的问题,提取出规则,抽象为一种“语言”。比如加减法运算,规则为:由数值和±符号组成的合法序列,“1+3-2” 就是这种语言的句子。

解释器就是要解析出来语句的含义。但是如何描述规则呢?

文法(语法)规则:

文法是用于描述语言的语法结构的形式规则。

expression ::= value | plus | minus
plus ::= expression ‘+’ expression   
minus ::= expression ‘-’ expression  
value ::= integer

注意: 这里的符号“::=”表示“定义为”的意思,竖线 | 表示或,左右的其中一个,引号内为字符本身,引号外为语法。

上面规则描述为 :

表达式可以是一个值,也可以是plus或者minus运算,而plus和minus又是由表达式结合运算符构成,值的类型为整型数。

抽象语法树:

在计算机科学中,抽象语法树(AbstractSyntaxTree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。

用树形来表示符合文法规则的句子。

6.11.2 结构

解释器模式包含以下主要角色。

  • 抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。

  • 终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。

  • 非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。

  • 环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。

  • 客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。

6.11.3 案例实现

【例】设计实现加减法的软件

代码如下:

//抽象角色AbstractExpression
public abstract class AbstractExpression 
    public abstract int interpret(Context context);


//终结符表达式角色
public class Value extends AbstractExpression 
    private int value;

    public Value(int value) 
        this.value = value;
    

    @Override
    public int interpret(Context context) 
        return value;
    

    @Override
    public String toString() 
        return new Integer(value).toString();
    


//非终结符表达式角色  加法表达式
public class Plus extends AbstractExpression 
    private AbstractExpression left;
    private AbstractExpression right;

    public Plus(AbstractExpression left, AbstractExpression right) 
        this.left = left;
        this.right = right;
    

    @Override
    public int interpret(Context context) 
        return left.interpret(context) + right.interpret(context);
    

    @Override
    public String toString() 
        return "(" + left.toString() + " + " + right.toString() + ")";
    


///非终结符表达式角色 减法表达式
public class Minus extends AbstractExpression 
    private AbstractExpression left;
    private AbstractExpression right;

    public Minus(AbstractExpression left, AbstractExpression right) 
        this.left = left;
        this.right = right;
    

    @Override
    public int interpret(Context context) 
        return left.interpret(context) - right.interpret(context);
    

    @Override
    public String toString() 
        return "(" + left.toString() + " - " + right.toString() + ")";
    


//终结符表达式角色 变量表达式
public class Variable extends AbstractExpression 
    private String name;

    public Variable(String name) 
        this.name = name;
    

    @Override
    public int interpret(Context ctx) 
        return ctx.getValue(this);
    

    @Override
    public String toString() 
        return name;
    


//环境类
public class Context 
    private Map<Variable, Integer> map = new HashMap<Variable, Integer>();

    public void assign(Variable var, Integer value) 
        map.put(var, value);
    

    public int getValue(Variable var) 
        Integer value = map.get(var);
        return value;
    


//测试类
public class Client 
    public static void main(String[] args) 
        Context context = new Context();

        Variable a = new Variable("a");
        Variable b = new Variable("b");
        Variable c = new Variable("c");
        Variable d = new Variable("d");
        Variable e = new Variable("e");
        //Value v = new Value(1);

        context.assign(a, 1);
        context.assign(b, 2);
        context.assign(c, 3);
        context.assign(d, 4);
        context.assign(e, 5);

        AbstractExpression expression = new Minus(new Plus(new Plus(new Plus(a, b), c), d), e);

        System.out.println(expression + "= " + expression.interpret(context));
    

6.11.4 优缺点

1,优点:

  • 易于改变和扩展文法。

    由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言。

  • 实现文法较为容易。

    在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂。

  • 增加新的解释表达式较为方便。

    如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无须修改,符合 “开闭原则”。

2,缺点:

  • 对于复杂文法难以维护。

    在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护。

  • 执行效率较低。

    由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。

6.11.5 使用场景

  • 当语言的文法较为简单,且执行效率不是关键问题时。

  • 当问题重复出现,且可以用一种简单的语言来进行表达时。

  • 当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候。

以上是关于Java开发设计模式 09:装饰器模式的主要内容,如果未能解决你的问题,请参考以下文章

Java 设计模式之装饰器学习与掌握

Java IO设计模式总结

Java设计模式之——装饰器模式

java设计模式之装饰器模式

java设计模式之 装饰器模式

装饰器设计模式初探(Java实现)