装饰器模式


定义

本节我们要学习的设计模式叫做——装饰器模式,何为装饰器模式?

假如我们现在有一个视频 video 需要播放,如果在 video 播放上加上弹幕,那我们的视频播放还是之前的播放,只不过在原有播放功能上加入了弹幕功能,同样地,我们可以在视频播放上加入 3D 效果,这样就有了 3D 播放功能。这种在原有基础上进行装饰,来添加新的功能的模式其实就叫做装饰器模式,简称装饰模式。最直观地就是我们买房后的装修,无非是对原有对象(房子)的一种额外装饰,我们在开头就讲过,软件设计模式其实是从建筑领域延申过来的,这样看来,一点没错。

下面我们列举一个比较简单的例子,争取以最通俗的语言进行模式的讲解。

使用实例

本节中示例程序的功能是为了给视频播放添加弹幕功能,所谓的弹幕,我们这里比较简单只是单纯的演示使用,就是在原有视频内容的基础上添加弹幕内容,听起来是不是很炫,其实比较简单,我们一步步来看,首先是我们的示例程序的类图说明:

MediaPlay 媒体播放抽象类

媒体播放抽象类是具有媒体播放功能的抽象类。

getMediaName 用于获取播放的媒体文件名,属于抽象方法,需要子类去实现;getMediaSeconds 用于获取播放的媒体的播放时长,也属于抽象方法,需要子类自行实现;getMediaContent 用于获取播放的媒体的内容,同样需要子类实现。play 是模拟的媒体播放方法,这里我们每隔一秒打印输出媒体内容,具体代码如下:

/**
 * 定义 媒体播放 抽象类,用于模拟多媒体播放功能(Component 抽象构件角色)
 */
public abstract class MediaPlay {

    public abstract String getMediaName(); // 获取要播放的媒体文件名称

    public abstract int getMediaSeconds(); // 获取要播放的媒体文件的播放时长(s)

    public abstract String getMediaContent(); // 获取需要播放的媒体文件内容

    // 模拟媒体播放
    public final void play() {
        System.out.println("Media:" + getMediaName() + "(累计时长:" + getMediaSeconds() + " 秒) 正在播放");
        try {
            for (int i = 1; i <= getMediaSeconds(); i++) {
                Thread.sleep(1000);
                System.out.println("当前播放第 " + i + " 秒," + getMediaContent() + "...");
            }
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

VideoPlay 具体被装饰的类

上面的 MediaPlay 抽象类,如果仅仅查看代码是看不出完整端倪的,所以我们再看下它的子类——VideoPlay 具体实现类,该类的构造接收两个参数,分别是:视频名、视频内容。VideoPlay 类代表视频播放具体类,属于被装饰的角色,我们后面的弹幕视频播放就是修饰的该视频播放类,关键代码如下:

/**
 * 视频播放类,模拟视频播放功能,属于真正被装饰的角色
 */
public class VideoPlay extends MediaPlay {

    private String videoContent; // 播放的视频内容
    private String videoName; // 播放的视频名称

    public VideoPlay(String videoName, String videoContent) {
        this.videoName = videoName;
        this.videoContent = videoContent;
    }

    @Override
    public String getMediaName() { // 获取媒体文件(视频)名称
        return videoName;
    }

    @Override
    public int getMediaSeconds() { // 获取媒体文件的播放时长
        return videoContent.length();
    }

    @Override
    public String getMediaContent() { // 获取媒体文件的播放内容
        return videoContent;
    }
}

BarrageVideoPlay 弹幕播放抽象类

原有的视频播放太过单调,我们现在想在其基础上加入弹幕播放的功能,BarrageVideoPlay 抽象类用于定义我们要实现的弹幕播放功能,该类往往只是一个抽象类,内部持有 MediaPlay 类的引用,所以其子类可以比较方便的保留使用 MediaPlay 的既有功能,该类的代码实现如下:

// 弹幕视频播放:定义弹幕播放的抽象角色,具体的弹幕实现交给子类
public abstract class BarrageVideoPlay extends MediaPlay{

    protected MediaPlay mediaPlay;

    public BarrageVideoPlay(MediaPlay mediaPlay) {
        this.mediaPlay = mediaPlay;
    }
}

BarrageRedVideoPlay 弹幕播放的具体实现类

该类是 BarrageVideoPlay 抽象类的子类,内部具体实现了“弹幕功能”,父类持有 MediaPlay 类的引用,可以直接调用父类方法,只不过在方法内部可以实现装饰功能,添加一些装饰操作,具体实现类如下:

// 实现弹幕播放的类,具体的装饰器的实现类,这里我们使用红色字体进行弹幕视频播放
public class BarrageRedVideoPlay extends BarrageVideoPlay{

    public BarrageRedVideoPlay(MediaPlay mediaPlay) {
        super(mediaPlay);
    }

    @Override
    public String getMediaName() {
        return mediaPlay.getMediaName() + "(已开启弹幕)";
    }

    @Override
    public int getMediaSeconds() {
        return mediaPlay.getMediaSeconds();
    }

    @Override
    public String getMediaContent() {
        return "---+++*** " + mediaPlay.getMediaContent() + "(弹幕中)---+++***";
    }
}

Main 测试类

接下来,我们进行测试,分别测试不加弹幕和添加弹幕的视频播放功能,测试代码如下:

MediaPlay mediaPlay = new VideoPlay("射雕英雄传", "郭靖大战欧阳锋");
MediaPlay mediaPlay1 = new BarrageRedVideoPlay(mediaPlay);
mediaPlay.play(); // 不加装饰时
mediaPlay1.play(); // 添加弹幕装饰时

测试结果输出如下:

Media:射雕英雄传(累计时长:7 秒) 正在播放
当前播放第 1 秒,郭靖大战欧阳锋…
当前播放第 2 秒,郭靖大战欧阳锋…
当前播放第 3 秒,郭靖大战欧阳锋…
当前播放第 4 秒,郭靖大战欧阳锋…
当前播放第 5 秒,郭靖大战欧阳锋…
当前播放第 6 秒,郭靖大战欧阳锋…
当前播放第 7 秒,郭靖大战欧阳锋…
…
Media:射雕英雄传(已开启弹幕)(累计时长:7 秒) 正在播放
当前播放第 1 秒,—+*** 郭靖大战欧阳锋(弹幕中)—+***…
当前播放第 2 秒,—+*** 郭靖大战欧阳锋(弹幕中)—+***…
当前播放第 3 秒,—+*** 郭靖大战欧阳锋(弹幕中)—+***…
当前播放第 4 秒,—+*** 郭靖大战欧阳锋(弹幕中)—+***…
当前播放第 5 秒,—+*** 郭靖大战欧阳锋(弹幕中)—+***…
当前播放第 6 秒,—+*** 郭靖大战欧阳锋(弹幕中)—+***…
当前播放第 7 秒,—+*** 郭靖大战欧阳锋(弹幕中)—+***…

可以看到,添加弹幕装饰之后,我们的视频播放起来功能更加丰富了,这就是装饰器模式的好处,在不改变原有功能的基础上添加额外的装饰功能。

组成角色

装饰器模式类图如上图所示,其中包含如下几个角色:

  • 抽象构件(Component ):Component 是一个接口或者抽象类,也是最原始的对象,属于模式核心角色。用于定义一些抽象的接口或功能,以便后面的 ConcreteComponent 和 ConcreteDecorator 角色去实现;
  • 具体构件(ConcreteComponent):ConcreteComponent 是最原始、最基本的接口或抽象类 Component 的实现,在模式中充当被装饰的角色,也就说我们模式要装饰的对象就是 ConcreteComponent;
  • 抽象装饰角色(Decorator):Decorator 一般是一个抽象类,实现接口或者抽象方法,其内部不一定有抽象方法定义,有可能只是单纯继承下 Component 抽象构件;但是其内部一般都有一个 Component 角色的引用,表示 Decorator 需要装饰的对象,一般该对象是 private 或者 protected 声明;
  • 具体装饰器角色ConcreteDecorator):具体的装饰器类,继承 Decorator 抽象装饰器角色,实现了 Component 抽象角色中定义的接口(API)。

总结

这节,我们学习了装饰器模式的简单使用,总结下如下所示


Author: Re:0
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source Re:0 !
 Previous
享元模式 享元模式
定义这节中我们将介绍一种新的结构型模式——享元模式 (Flyweight Pattern),Flyweight 模式是以共享的模式来支持大量细粒度对象的复用。听起来可能有点绕,Java 中的 String 就是享元模式的一个应用:
2022-03-09
Next 
组合模式 组合模式
定义这节我们将介绍一种全新的设计模式——组合模式。想起“组合”二字,自然联想到了很多,比如:文件和文件夹、容器和组件、火车和车厢、大树的枝干和叶子等等,大自然中组合的例子数不胜数。
2022-03-09
  TOC