概念
装饰器模式(Decorator
Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
具体实现
- 提供一个A接口,装饰类和原类都需要实现该接口
- 被装饰类实现了A接口,并已经实现了一部分功能
- 装饰类也实现了A接口,并在实现A接口的同时,内部还嵌入了A对象,用来日后存储被装饰类
- 调用步骤:
- 新建一个具体的装饰子类,必须继承装饰类,在继承的同时,需要重写被装饰类里需要被装饰的方法。
- 在重写被装饰类的时候,调用被装饰类的方法,并随之添加一些功能。
- 在使用时,只需要调用装饰类的子类,并将被装饰类赋值进去,到时直接调用对应的方法即可
介绍
意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
何时使用:在不想增加很多子类的情况下扩展类。 如何解决:将具体功能职责划分,同时继承装饰者模式。 关键代码: 1、Component类充当抽象角色,不应该具体实现。 2、修饰类引用和继承 Component 类,具体扩展类重写父类方法。
应用实例: 1、孙悟空有 72变,当他变成"庙宇"后,他的根本还是一只猴子,但是他又有了庙宇的功能。2、不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。
优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
缺点:多层装饰比较复杂。 使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。 注意事项:可代替继承。
示例
我们先假设有这么一个情景,我们每天都去打篮球。类似下面这种代码:
public class Basketball{
@Override
public void run() throws InterruptedException {
System.out.println("我开始打篮球了");
Thread.sleep(new Double(Math.random() * 5000).longValue() );
System.out.println("我篮球打完了");
}
}
但是呢,突然有一天,我想在调用run()方法时,同时又能知道我每天篮球打了多长时间,但又不想更改之前的方法,最简单的实现方式,就是继承之前的代码,然后再之前的代码上修改,同时调用之前的run(),(千万不要自己再重新写一个run(),这就违背了DRY原则)。就像这样:
public class BasketballTest extends Basketball{
@Override
public void run() throws InterruptedException {
Date start = new Date();
super.run();
Date end= new Date();
System.out.println("你篮球打了:"+(end.getTime()-start.getTime())/1000.0 + "秒");
}
}
但问题来了,如果我们一天同时不只是打篮球,还同时进行了唱、跳、Rap、篮球,难道我们要写四个类来继承之前的类吗?这样加上就有八个类了,代码臃肿,且耦合性强,复用性弱。
解决办法
我们可以新建一个装饰类,然后在装饰类的子类里面实现我们的需求。
我们可以这么比如,装饰类可以想象成一个运动手环,运动手环又分为很多种,其中一种是小米手环,我们可以使用小米手环来满足我们的需求,之前我们做的那些运动就是被装饰类,而运动手环就是装饰类,小米手环就是装饰子类。
这时可能有人有疑问,为什么不直接定义小米手环,还需要多出来运动手环这一层?因为运动手环其实定义的是装饰类的规范,今天我们是记录运动时间,如果明天我们需要记录心跳怎么办?
大家可以先看一下框架图,具体的我们后面会详细的讲到
这是装饰器设计模式比较官方的类图,大家也可以参考一下
具体代码
我们先定义好唱、跳、Rap、篮球对应的接口,以及他们各自的实现类。(抛出异常是因为我们调用了Sleep方法)
public interface Sport {
void run() throws InterruptedException;
}
public class Sing implements Sport{
@Override
public void run() throws InterruptedException {
System.out.println("我开始唱了");
Thread.sleep(new Double(Math.random() * 5000).longValue() );
System.out.println("我唱完了");
}
}
public class Jump implements Sport{
@Override
public void run() throws InterruptedException {
System.out.println("我开始跳了");
Thread.sleep(new Double(Math.random() * 5000).longValue() );
System.out.println("我跳完了");
}
}
public class Rap implements Sport{
@Override
public void run() throws InterruptedException {
System.out.println("我开始Rap了");
Thread.sleep(new Double(Math.random() * 5000).longValue() );
System.out.println("我Rap完了");
}
}
public class Basketball implements Sport{
@Override
public void run() throws InterruptedException {
System.out.println("我开始打篮球了");
Thread.sleep(new Double(Math.random() * 5000).longValue() );
System.out.println("我篮球打完了");
}
}
之后定义运动手环类,因为这个类需要调用之前的唱跳rap方法,所以得实现Sport类,并且包含一个Sport属性,让调用的时候将对应的子类传进去,我们就能调用相对应的唱跳Rap方法了
public class SportBand implements Sport{
Sport sport;
SportBand(Sport sport){
this.sport = sport;
}
@Override
public void run() throws InterruptedException {
this.sport.run();
}
}
接下来定义装饰子类,用于实现具体功能
public class MiBand extends SportBand{
MiBand(Sport sport) {
super(sport);
}
@Override
public void run() throws InterruptedException {
Date start = new Date();
sport.run();
Date end= new Date();
System.out.println("你"+sport.getClass().getSimpleName()+"花了:"+(end.getTime()-start.getTime())/1000.0 + "秒");
System.out.println("------------------");
}
}
Demo代码
public class Demo {
public static void main(String[] args) throws InterruptedException {
//我买了一个小米手环,戴上它准备去唱,这一整个行为被称为运动,所以赋值给运动对象
Sport sing= new MiBand(new Sing());
//我买了一个小米手环,戴上它准备去跳
Sport jump = new MiBand(new Jump());
//我买了一个小米手环,戴上它准备去Rap
Sport rap = new MiBand(new Rap());
//我买了一个小米手环,戴上它去准备打篮球
Sport basketball = new MiBand(new Basketball());
//我开始唱跳Rap和篮球了,小米手环也发挥作用,记录了我分别用了多久
sing.run();
jump.run();
rap.run();
basketball.run();
}
}
实现效果:
IO流举例
我们查看到IO流的源码时发现了他们类图的框架如下
你们看这个框架,里面的A,像不像我们之前说到的唱、跳、Rap和篮球。B像不像我们提到的运动手环,而C就是他们的手环的实现类:小米手环、荣耀手环等等。
在IO流里面,就是通过FilterInputStream来装饰FileInputStream、ByteArrayInputStream等等。
到时候我们调用的时候,肯定会出现
InputStream bufferedInput = new BufferedInputStream(new FileInputStream(filename));
像不像我们之前的
Sport sing= new MiBand(new Sing());
InputStream对应Sport,也就是最原始的接口
MiBand对应BufferedInputStream,也就是C的位置。
Sing对应FileInputStream,也就是A的位置。
而MiBand继承了SportBand,BufferedInputStream也继承了FilterInputStream。
//java源码里BufferedInputStream继承自FilterInputStream
class BufferedInputStream extends FilterInputStream
Java编程思想中这么写到
FilterInputStream 和 FilterOutputStream(这两个名字不十分直观)提供了相应的装饰器接口,用于控制一个特定的输入流( InputStream)或者输出流(OutputStream)。它们分别是从 InputStream 和OutputStream衍生出来的。此外,它们都属于抽象类,在理论上为我们与一个流的不同通信手段都提供了一个通用的接口。事实上, FilterInputStream和 FilterOutputStream 只是简单地模仿了自己的基础类,它们是一个装饰器的基本要求。