一、 装饰器模式引出
通常为了扩展一个类(扩展方法),有两种方式
方式1:要么直接修改类添加相应的功能,要么使用继承方式派生对应的子类来扩展
特点:采用适配器的模式,通过继承派生对应的子类来扩展功能
缺点:由于继承为类引入静态特征(编译期确定了),并且随着扩展功能的增多,会导致子类数目的急剧增多,子类会很膨胀。
显然方式1并不可取,引出了第二种模式
方式2:使用对象组合的方式(关联),也就是今天我们要讲的装饰器模式
装饰模式设计目的:实现动态(运行时选择)的为对象添加功能,即从一个对象外部来给对象添加功能。
装饰器模式特点:就是基于对象组合的方式,也即动态组合的方式,可以很灵活的给对象添加所需要的功能;动态是手段,组合才是目的。
优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
从设计层面理解:装饰模式是通过把复杂的功能简单化,分散化,然后在运行期间,根据需要来动态组合的这样一个模式。
所以在面向对象的设计中,而我们也应该尽量使用对象组合,而不是对象继承来扩展和复用功能。
装饰器模式实例说明:
1、孙悟空有 72 变,当他变成"庙宇"后,他其实还是一只猴子,但是他又有了庙宇的功能
2、不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里,这时画、玻璃和画框形成了一个物体
二、模式结构和说明
装饰模式的结构图
装饰器设计模式最初来源于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流:点击打开链接,点击打开链接,点击打开链接