定义
本节我们要学习的设计模式叫做——装饰器模式,何为装饰器模式?
假如我们现在有一个视频 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)。
总结
这节,我们学习了装饰器模式的简单使用,总结下如下所示