吴志勇的博客 吴志勇的博客
  • h5

    • HTML5&CSS3
  • scss

    • css预处理语言
  • JavaScript

    • JavaScript教程
    • Ajax
    • ES6教程
    • NodeJS
    • Typescript
  • 框架

    • Jquery
    • VUE
    • React
  • Swing专题
  • java基础
  • javaweb
  • 框架
  • 数据库
  • netty
  • 设计模式
  • 微服务及架构
  • 云原生
  • maven
  • 单元测试
工具
我的
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

吴志勇

......
  • h5

    • HTML5&CSS3
  • scss

    • css预处理语言
  • JavaScript

    • JavaScript教程
    • Ajax
    • ES6教程
    • NodeJS
    • Typescript
  • 框架

    • Jquery
    • VUE
    • React
  • Swing专题
  • java基础
  • javaweb
  • 框架
  • 数据库
  • netty
  • 设计模式
  • 微服务及架构
  • 云原生
  • maven
  • 单元测试
工具
我的
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Swing专题

  • java基础

  • javaweb

  • 框架

  • Maven
  • 单元测试
  • 动态代理
  • 数据库

  • netty

  • 设计模式

    • simple_factory
    • factory_method
    • abstract_factory
    • singleton
    • builder
    • prototype
    • adapter
    • decorator
      • 问题引入
        • 问题描述
        • 模式定义
        • 问题分析
      • 模式实现
        • 解决方案
        • 代码实现
        • 结构组成
      • 模式评价
        • 适用场景
        • 实际应用
        • 优点缺点
      • 参考资料
    • facade
    • bridge
    • proxy
    • composite
    • flyweight
    • observer
    • command
    • state
    • chain_of_responsibility
    • template_method
    • strategy
    • interpreter
    • mediator
    • visitor
    • memento
    • iterator
  • 微服务及架构

  • 云原生

  • Velocity模板引擎
  • 后端
  • 设计模式
wuzhiyong
2025-01-13

decorator

# 装饰模式

# 问题引入

# 问题描述

想要给人一个较好的印象,穿衣搭配得体干净是不可或缺的。我们可以使用程序写一个可以给人搭配不同服饰的系统,比如类似QQ、电商平台或游戏都有的Avatar系统,实现给人搭配嘻哈服或白领装的功能。程序预期的输出效果如下图所示。

为实现这一程序,可先定义Person类,将服饰装扮都写为Person类的接口方法。但如若要在此基础上增加“超人”的装扮,就需要修改Person类,会违背开放-封闭原则,因此应考虑将服饰单独为类。

同时,由于穿衣过程不能在众目睽睽之下完成,应当考虑在类内部组装完毕,再显示。而类内部的服饰组装过程不是固定的,因为通过服饰组合出一个有个性的人完全可以有无数种方案,如上衣大T恤,既可以搭配球鞋,也可以搭配皮鞋。同时服饰的装扮的先后顺序也有一定讲究,毕竟先穿内裤后穿外裤和先穿外裤再穿内裤是截然不同的效果。也就是说,我们需要将所需功能按正确的顺序串联起来进行控制,此时可以考虑使用装饰模式。

# 模式定义

装饰模式(Decorator Pattern)是指创建一个装饰类,来包装原有的类,从而实现动态地向一个现有的对象添加一些额外的职责,同时不改变其原有的结构。装饰模式比生成子类更为灵活。

# 问题分析

装饰模式用在此问题较为直观,衣服、鞋子、领带、披风等服饰都可以理解为对人的装饰。在实现Person类表示人的基础上,定义具体的服饰类实现对Person类进行服装装饰,装扮完再统一显示,这样既避免了穿每一件衣服的过程直接在客户端实现,同时又保留服装搭配的灵活性。

# 模式实现

# 解决方案

使用装饰模式来解决问题。

  1. 创建抽象的接口类Component,定义给对象动态添加职责的公共接口(在此例中,由于具体的接口只有一个,所以该步也可省略);
  2. 创建具体的接口Person(Concrete Component),继承于抽象接口类Component,同时:
    • 定义方法Show()用于显示装扮结果;
  3. 创建抽象的装饰类Finery(Decorator),继承于接口类Person(一般来说继承于抽象接口类Component,由于此例只有一个接口,故继承于具体接口类),同时:
    • 定义方法Decorate(component)用于进行装扮过程;
    • 覆写Show()具体装扮结果的显示;
  4. 创建系列具体的服饰类(Concrete Decorator),如Tshirts,BigTrouser等,继承于抽象装饰类Finery,实现具体的装饰对象,同时:
    • 覆写Show()具体装扮结果的显示。

# 代码实现

此处我们使用Java语言来实现这一方案,C#语言实现可见原书原版,本项目的所有语言实现可见本项目Github仓库,其中包括:C++ (opens new window),Java (opens new window),python (opens new window),读者可按需参阅。

定义Person类,相当于ConcreteComponent。

public class Person {
    public Person() {}

    private String name;
    public Person(String name) {
        this.name = name;
    }

    public void show() {
        System.out.println("装扮的" + this.name);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

定义Finery类,相当于Decorator。

public class Finery extends Person {
    protected Person component;

    public void decorate(Person component) {
        this.component = component;
    }

    @Override
    public void show() {
        if(component != null) {
            component.show();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

具体的服饰类,相当于ConcreteDecorator,这里以Tshirts、Sneakers、BigTrouser为例,其余类类似,代码省略。

public class TShirts extends Finery{
    @Override
    public void show() {
        System.out.println("大T恤");
        super.show();
    }
}

public class Sneakers extends Finery{
    @Override
    public void show() {
        System.out.println("运动鞋");
        super.show();
    }
}

public class BigTrouser extends Finery{
    @Override
    public void show() {
        System.out.println("跨裤");
        super.show();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

客户端如下。

public class DecoratorClient {
    public static void main(String[] args) {
        Person person = new Person("小菜");
        System.out.println("第一种装扮:");

        Sneakers sneaker = new Sneakers();
        BigTrouser trouser = new BigTrouser();
        TShirts tshirt = new TShirts();
        
        sneaker.decorate(person);
        trouser.decorate(sneaker);
        tshirt.decorate(trouser);
        tshirt.show();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

运行结果如下。

第一种装扮:
大T恤
垮裤
破球鞋
装扮的小菜
1
2
3
4
5

# 结构组成

装饰模式由四类主要角色组成:

  1. 实体接口:对象接口的定义,可以为对象动态添加职责,在这个例子中职责具体为形象展示的过程,由于Person类在此程序中只有形象展示这一个职责,Person类既是访问接口,也是实体类;
  2. 实体类:在这个例子中具体为Person类;
  3. 装饰抽象类:继承实体接口,动态扩展其职责,在这个例子中具体为服饰抽象类;
  4. 具体装饰类:装饰的具体实现,在这个例子中具体为各类服饰类,如大T恤,大垮裤等。

结构示意图如下

案例UML

装饰模式的通用结构示意图如下

装饰模式UML

# 模式评价

# 适用场景

可以在不生成很多子类的情况下扩展类,适用于扩展类需求较多,而又不想引起子类膨胀的场景。

# 实际应用

  • 通知信息有多种渠道,如通过短信、微信、QQ、邮件等。不同的信息会采用不同的多种渠道组合进行通知,此时若对每一个组合都建立子类,会造成子类数量爆炸,可以考虑装饰器模式。

# 优点缺点

装饰模式的优点有

  • 把类中的装饰功能从类中搬移去除,很好地简化了原有的类;
  • 有效地把类的核心职责和装饰功能区分开了,可以去除相关类中重复的装饰逻辑;
  • 装饰类和被装饰类可以独立发展,不会相互耦合;
  • 无需创建新子类即可实现对类功能的动态扩展;
  • 支持运行时添加或删除对象的功能;
  • 满足“单一职责原则”,可将实现许多不同行为的类拆分为多个较小的类。

装饰模式的缺点有

  • 在封装器栈中删除特定封装器比较困难;
  • 较难实现行为不受到先后顺序影响的装饰;
  • 各装饰层的代码相对冗余。

# 参考资料

  1. 《深入设计模式》
上次更新: 2025-02-06 00:56:14

← adapter facade→

Copyright © 2020-2025 wuzhiyong
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式