5分钟弄懂装饰器设计模式 (结合唱、跳、Rap、篮球,结合实例,便于理解)

概念

装饰器模式(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 只是简单地模仿了自己的基础类,它们是一个装饰器的基本要求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值