java程序员从笨鸟到菜鸟之(三十八)IO流之装饰器模式初探

装饰器设计模式

、 装饰器模式引出

通常为了扩展一个类(扩展方法),有两种方式

方式1:要么直接修改类添加相应的功能,要么使用继承方式派生对应的子类来扩展

特点:采用适配器的模式,通过继承派生对应的子类来扩展功能

缺点:由于继承为类引入静态特征(编译期确定了),并且随着扩展功能的增多,会导致子类数目的急剧增多,子类会很膨胀。

显然方式1并不可取,引出了第二种模式

方式2:使用对象组合的方式(关联),也就是今天我们要讲的装饰器模式

装饰模式设计目的:实现动态(运行时选择)的为对象添加功能,即从一个对象外部来给对象添加功能。

装饰器模式特点:就是基于对象组合的方式,也即动态组合的方式,可以很灵活的给对象添加所需要的功能;动态是手段,组合才是目的

优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

从设计层面理解:装饰模式是通过把复杂的功能简单化,分散化,然后在运行期间,根据需要来动态组合的这样一个模式。

所以在面向对象的设计中,而我们也应该尽量使用对象组合,而不是对象继承来扩展和复用功能

装饰器模式实例说明: 

1、孙悟空有 72 变,当他变成"庙宇"后,他其实还是一只猴子,但是他又有了庙宇的功能 

2、不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里,这时画、玻璃和画框形成了一个物体

、模式结构和说明

  装饰模式的结构图

java程序员从笨鸟到菜鸟之(三十八)IO流之装饰器模式初探_第1张图片


装饰器设计模式最初来源于jdk1.8中对I/O流的理解,所以以I/O流为例,文末附上两个例子

Component:---InputStream

说明:组件对象的接口(或抽象类),可以给这些对象动态的添加职责;

ConcreteComponent:---FileInputStream、DataInputStream等

说明:具体的组件对象实现了组件接口,该对象通常就是被装饰器装饰原始对象,称为待包装对象被装饰者,被装饰者初始状态可以设置为自己的装饰,可以给这个对象添加最初的功能

Decorator:---FilterInputStream

说明:所有装饰器的基类(父类)—抽象类,需要定义一个与组件接口一致的接口

补充:装饰器的基类为什么要(继承)实现一个与组件接口一致的接口(抽象类)?

      解答:主要是为了实现装饰器功能的复用,即具体的装饰器A可以装饰另外一个具体的装饰器B,因为装饰器基类也是一个Component,持有一个Component对象(成员变量),该对象其实就是被装饰的对象(接口多态的形式)。如果不继承组件接口类,则只能为某个组件添加单一的功能,即装饰器对象不能再装饰其它的装饰器对象。通过FilterInputStream源码来看,有一个成员变量InputStream的对象,继承了组件接口(抽象类)InputStream的方法,其实还是抽象方法。

ConcreteDecorator:----DataInputStream、BufferedInputStream等

说明:具体的装饰器类,实现具体要向被装饰对象添加的功能,即:用来装饰具体的组件对象或者另外一个具体的装饰器对象。

分析:具体的装饰器类的每一个构造方法的形式参数都有InputStream接口(抽象类),表明只要是该InputStream的实现类的对象都可以传递进去;且构造方法中均都有一个super(InputStream in)---指向了装饰器基类的构造方法(主要是给装饰器基类的成员变量赋值,当添加新功能时,通过super.方法名把原来的功能也封装上),上面我们提到过此装饰器基类有一个成员变量:InputStream的对象。

举例j说明:参考依据---底层源码,有兴趣的话可以自己分析下

过程:在创建具体装饰器对象(BufferedInputStream)中把待包装--待装饰对象(InputStream的具体子实现类)作为构造方法的实参传入,由于(具体装饰器---装饰器基类的子实现类)BufferedInputStream继承了装饰器基类(FilterInputStream),首先通过super()会调用装饰器基类FilterInputStream的构造方法,而此此装饰器基类有一个成员变量(组件的接口对象---InputStream),通过装饰器基类的构造方法对其成员变量初始化;在具体装饰器类方法中可以调用被装饰对象方法的方式:通过super.方法名(参数)的方式。在具体装饰器类方法中可以调用被装饰对象方法的原因抽象装饰基类实现了接口,并且有一个成员变量为此接口的对象,super已经由构造传递并指向了具体的某一个被装饰者类,那么调用的即为被装饰类的方法,实际上这里全是重写了被装饰对象的方法(原因:可能原来的方法不是所需)

感悟:不要被表面所迷惑,背后的力量很强大,也很复杂,共同支撑和维护着

装饰作用:通过装饰者的多层装饰,把细节变得丰富,在调用前后可以实现自己的输出,实现功能的扩展

由此引出装饰器模式的特点:----判断一种模式是不是装饰器模式

百度百科中概念:装饰模式指的是在不必改变原类文件使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

说明:原类---被装饰的类;使用继承--被装饰类的子类继承被装饰的类

特征

(1) 装饰对象和真实对象(被装饰对象)有相同的接口,这样客户端对象就能"以和真实对象相同的方式"和装饰对象交互。
(2) 装饰对象包含一个真实对象(被装饰对象)的引用(构造方法传入)
(3) 装饰对象接受所有来自客户端的请求,它把这些请求转发给真实的对象。
(4) 装饰对象可以在转发这些请求以前或以后增加一些附加功能(super.方法(参数)的位置)。这样就确保了在运行时,不用修改给定对象的结构(继承之类的)就可以在外部增加附加的功能。而在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展。

百度百科链接:点击打开链接

要点、适用性、优点、缺点:点击打开链接,缺点之一:正因为随意组合,所以出现了一些不合逻辑(张冠李戴)

装饰器和适配器模式对比:点击打开链接

最初看的:点击打开链接

大神:点击打开链接

集合框架的装饰器模式:点击打开链接

、为了加深自己对装饰器模式的理解,下面仅以两个例子作为补充说明

例子1

       情景:手机制造商原来有一款PhoneX的产品,只能打电话(乔布斯会不会出来打我),现在随着网络的发展,人们已经不再满足仅仅打电话的需求,所以若基亚的产品急剧下降,为此项目部开了几次会议,决定增加看电影、听音乐的功能,代码设计如下:

      Phone---------------所有手机的抽象类(接口)

package www.wzj.decorateDesign;
public interface Phone {
    void call();//定义一个抽象的方法
}

      InPhoneX-------------手机类的一个具体子实现类(继承自Phone),打电话的功能

package www.wzj.decorateDesign;
public class InphoneX implements Phone {
    /**
     * InphoneX是一个被装饰者对象
     * 目前只有打电话的功能,一段时间以后,产品要升级
     * 增加如下的功能:现在想看电影,听音乐等
     */
    @Override
    public void call() {
        System.out.println("打电话");
    }
}

      DecoratePhone-----手机具体装饰者类的抽象类(实现Phone的接口,原因如上所述)

package www.wzj.decorateDesign;

public class DecoratePhone implements Phone {

    Phone phone ;
    //传入具体的被装饰者,转发(继承)"装饰"装饰者的任务,同时封装装饰器本身的功能

    // 即给予其在运行的状态中动态的扩展功能
    public DecoratePhone(Phone phone) {
        this.phone = phone;
    }
    @Override
    public void call() {
        phone.call();//重点:封装被装饰者本身的方法(通过继承的方式传递下去)
    }
}

      MusicPhone---------手机具体装饰者类的抽象类的子实现类,有打电话的功能

package www.wzj.decorateDesign;

public class MusicPhone extends DecoratePhone {

    //需要注意的是:成员变量 Phone phone;也被继承过来了
    public MusicPhone(Phone phone) {
        super(phone);//调用被装饰者的对象和具体的具体的装饰器对象
    }
    /**
     * MusicPhone:装饰者对象,增加被装饰者的功能
     * 由于Inphone只有打电话的功能,现在想增加功能
     *
     */
    @Override
    public void call() {
        super.call();//调用被装饰者的对象的功能
        System.out.println("听音乐");
    }
}

      VideoPhone---------手机具体装饰者类的抽象类的子实现类,有看电影的功能

package www.wzj.decorateDesign;

public class VideoMusic extends DecoratePhone {
    public VideoMusic(Phone phone) {
        super(phone);
    }
    /**
     * VideoPhone:装饰者对象
     * 来给InphoneX增加看电影的功能
     */
    @Override
    public void call() {
        super.call();
        System.out.println("看电影");
    }
}

测试

package www.wzj.decorateDesign;

import java.io.IOException;
public class DecorateDesignTest {
    public static void main(String[] args) throws IOException {

        new MusicPhone(new InphoneX()).call();

        new MusicPhone(new VideoMusic(new InphoneX())).call();

        Runtime runtime = Runtime.getRuntime();//项目要求:需求分析、概要和详细设计
        runtime.exec("mspaint");//打开画图软件
        runtime.exec("calc");//打开计算器
        //runtime.exec("shell");//Linux的应用,打开shell脚本
    }
}

 重点:装饰器基类的设计,如何保证扩展新功能的同时,保留原来的功能(如果需要的话),因为没有显示的调用此方法

咖啡的配置(不同口味),对应的价格

Car的例子:点击打开链接,点击打开链接

IO流:点击打开链接,点击打开链接,点击打开链接

你可能感兴趣的:(设计模式)