设计模式实战-装饰器模式,教你怎么为代码添砖加瓦
1、定义
本节我们要学习的设计模式叫做——装饰器模式,何为装饰器模式?
假如我们现在有一个视频 video 需要播放,如果在 video 播放上加上弹幕,那我们的视频播放还是之前的播放,只不过在原有播放功能上加入了弹幕功能,同样地,我们可以在视频播放上加入 3D 效果,这样就有了 3D 播放功能。这种在原有基础上进行装饰,来添加新的功能的模式其实就叫做装饰器模式,简称装饰模式。最直观地就是我们买房后的装修,无非是对原有对象(房子)的一种额外装饰,我们在开头就讲过,软件设计模式其实是从建筑领域延申过来的,这样看来,一点没错。
下面我们列举一个比较简单的例子,争取以最通俗的语言进行模式的讲解。
2、使用实例
本节中示例程序的功能是为了给视频播放添加弹幕功能,所谓的弹幕,我们这里比较简单只是单纯的演示使用,就是在原有视频内容的基础上添加弹幕内容,听起来是不是很炫,其实比较简单,我们一步步来看,首先是我们的示例程序的类图说明:
2.1 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();
}
}
}
2.2 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;
}
}
2.3 BarrageVideoPlay 弹幕播放抽象类
原有的视频播放太过单调,我们现在想在其基础上加入弹幕播放的功能,BarrageVideoPlay 抽象类用于定义我们要实现的弹幕播放功能,该类往往只是一个抽象类,内部持有 MediaPlay 类的引用,所以其子类可以比较方便的保留使用 MediaPlay 的既有功能,该类的代码实现如下:
// 弹幕视频播放:定义弹幕播放的抽象角色,具体的弹幕实现交给子类
public abstract class BarrageVideoPlay extends MediaPlay{
protected MediaPlay mediaPlay;
public BarrageVideoPlay(MediaPlay mediaPlay) {
this.mediaPlay = mediaPlay;
}
}
2.4 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() + "(弹幕中)---+++***";
}
}
2.5 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 秒,—+*** 郭靖大战欧阳锋(弹幕中)—+***…
可以看到,添加弹幕装饰之后,我们的视频播放起来功能更加丰富了,这就是装饰器模式的好处,在不改变原有功能的基础上添加额外的装饰功能。
3、组成角色
装饰器模式类图如上图所示,其中包含如下几个角色:
- 抽象构件(Component ):Component 是一个接口或者抽象类,也是最原始的对象,属于模式核心角色。用于定义一些抽象的接口或功能,以便后面的 ConcreteComponent 和 ConcreteDecorator 角色去实现;
- 具体构件(ConcreteComponent):ConcreteComponent 是最原始、最基本的接口或抽象类 Component 的实现,在模式中充当被装饰的角色,也就说我们模式要装饰的对象就是 ConcreteComponent;
- 抽象装饰角色(Decorator):Decorator 一般是一个抽象类,实现接口或者抽象方法,其内部不一定有抽象方法定义,有可能只是单纯继承下 Component 抽象构件;但是其内部一般都有一个 Component 角色的引用,表示 Decorator 需要装饰的对象,一般该对象是 private 或者 protected 声明;
- 具体装饰器角色**(**ConcreteDecorator):具体的装饰器类,继承 Decorator 抽象装饰器角色,实现了 Component 抽象角色中定义的接口(API)。
4、总结
这节,我们学习了装饰器模式的简单使用,总结下如下所示:
上面的例子中,我们使用了弹幕这一装饰功能,倘若我们再需要实现一个 3D 播放的类,具体的类图设计又该如何呢,大家可以想一下?
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- ubuntu16.04搭建nfs服务的方法
- Ubuntu16.04搭建php5.6Web服务器环境
- Linux上通过SSH挂载远程文件系统方法详解
- Linux上创建、列出、删除Docker容器方法总结
- Centos 7.4服务器时间同步配置方法【基于NTP服务】
- PowerBI 通用万能日历模板,想干嘛就干嘛
- Linux持续集成自动化安装Maven的方法
- 在CentOS上安装搭建PHP+Apache+Mysql的服务器环境
- Centos7.4服务器安装apache及安装过程出现的问题解决方法
- ubuntu系统修改时区和时间的方法
- CentOS7 LNMP+phpmyadmin环境搭建 第一篇虚拟机及centos7安装
- flutter 输入框组件TextField的实现代码
- Mac通过不同终端SSH连接远程服务器的讲解
- Linux关于透明大页的使用与禁用介绍
- linux中使用grep命令详解