1、写在前面:为什么会有装饰模式?
装饰模式是为了解决继承强依赖性和出现大量子类不方便管理问题而出现的。
举例:大家都有自己的手机,而打电话时手机必须具备的基本功能。如果用代码实现的话,按照正常做法,我们首先定义手机类,具有打电话功能。但是随着发展,需求变化了,假设现在部分人们要求打电话要有彩铃,接下来如何设计?
我们利用以前学过的继承,新建彩铃手机类继承手机类可以具备父类的打电话功能,然后在子类新添加彩铃功能。但是以后人们的需求又变了,部分人由于做生意想在彩铃后边添加自己的广告,那么再用子类继承,以后又变了,部分人需要添加手机视频功能,再继承;又有部分人需要添加蓝牙功能,再继承;部分人又要添加照相功能,再继承。。。。
但是需求还会继续改变,使用继承可以解决问题,但是会出现巨量的子类,使得子类无法管理;而且由于继承带来的强依赖性,将来父类亲发生改变,依赖这个父类的子类都会发生改变。
所以,继承的方案可以,但是会带来新的问题,解决这些问题的方案,就是装饰模式。
2、什么是装饰模式?
GOF解释:动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。
百度百科:在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
个人理解:大量运用多态技术,通过为构造函数传递对象来实现对对象进行一层层的装饰,就好像穿衣服打扮一样,也是多用组合,少用继承的面向对象设计理念的体现。
3、装饰模式的UML类图与原理解析:
类图解析:
1)Component:抽象接口,用来统一规范准备被装饰的对象。
2)Concrete Component:具体被装饰类,定义一个将要被装饰的类。
3) Decorator:抽象装饰类,要求与被装饰类拥有一致的接口(这个是必须的,否则没有办法将装饰通过多态传递给 被装饰对象)。
4)Concrete Decorator:具体装饰类,负责给被装饰对象添加功能。
原理解析:利用多态,首先用接口类型定义被装饰类对象,如Component cp=new ConcreateComponent(),然后将cp作为具体装饰类的构造参数传给具体的装饰类并利用类之间的调用来让装饰类对其实现装饰功能。
4、装饰模式实例与代码实现:
还是用上面的手机需求为例:
手机超类:
<span style="font-size:14px;">//定义一个接口,规定实现它的类必须具有具有打电话的功能
public interface Phone {
public abstract void call();
}</span>
具体的手机类:
<span style="font-size:14px;">public class PhoneImpl implements Phone {
@Override
public void call() {
System.out.println("用手机打电话");
}
}</span>
抽象装饰类:
<span style="font-size:14px;">public abstract class PhoneDecorate implements Phone {
//定义一个Phone类型变量用于接收具体的手机对象
private Phone phone;
//构造函数要传递Phone类型变量
public PhoneDecorate(Phone phone) {
//因为Phone是接口,所以传递过来的必然是实现此接口的手机类的对象
//,然后用phone将传递过来的对象接收
this.phone = phone;
}
@Override
public void call() {
this.phone.call();
}
}</span>
具体装饰类:彩铃装饰类:
<span style="font-size:14px;">public class ColorPhoneDecorate extends PhoneDecorate {
//构造方法,需要传递具体的手机对象
public ColorPhoneDecorate(Phone phone) {
//调用父类的构造方法,将具体手机对象传递给父类构造
super(phone);
}
//重写call方法,添加播放彩铃功能,也就是对手机进行彩铃装饰
@Override
public void call() {
System.out.println("播放彩铃");
super.call();
}
}</span>
具体装饰类:广告装饰类:
<span style="font-size:14px;">public class GuangGaoPhoneDecorate extends PhoneDecorate {
//同彩铃装饰类为同一级,调用父类的构造方法
public GuangGaoPhoneDecorate(Phone phone) {
super(phone);
}
//重写call方法,进行播放广告装饰
@Override
public void call() {
super.call();
System.out.println("播放广告");
}
}</span>
测试类:<span style="font-size:14px;">public class PhoneTest {
public static void main(String[] args) {
//利用多态,声明一个具体手机对象
Phone p = new PhoneImpl();
p.call();
//给手机对象用彩铃修饰
PhoneDecorate pd = new ColorPhoneDecorate(p);
pd.call();
//给手机用广告功能修饰
pd = new GuangGaoPhoneDecorate(p);
pd.call();
//给手机用彩铃、广告功能双重修饰
pd = new GuangGaoPhoneDecorate(new ColorPhoneDecorate(p));
pd.call();
}
}</span>
可以看出,修饰模式总共有四部分组成,1)抽象被修饰类或接口;2)具体被修饰类;3)抽象修饰类;4)具体修饰类。
下面对代码调用过程进行分析:首先Phone p = new PhoneImpl()是利用多态声明一个具体的手机对象p;然后PhoneDecorate pd = new ColorPhoneDecorate(p),走的是ColorPhoneDecorate的构造方法,因为其构造方法内部为super(phone);即调用父类的构造方法,然后进入父类PhoneDecorate的构造方法,进行对phone的赋值。最后pd.call(),虽然pd是PhoneDecorate声明的,但是实质上是ColorPhoneDecorate类对象,所以调用方法走的是ColorPhoneDecorate的call方法,即用彩铃修饰过的方法,从而实现了彩铃修饰手机功能。
对于pd = new GuangGaoPhoneDecorate(new ColorPhoneDecorate(p)),首先走GuangGaoPhoneDecorate的构造方法,然后同上调用父类的构造方法,但是这里是将new ColorPhoneDecorate(p)赋值给phone,所以接下来走ColorPhoneDecorate的构造方法,走父类构造方法,将p赋值给phone,所以最后phone的值还是被赋予的p这个具体手机类。接下来开始调用方法,pd.call(),首先知道,pd实质上是 GuangGaoPhoneDecorate的对象,所以要调用 GuangGaoPhoneDecorate的call方法,所以首先执行其call方法中的super.call方法,调用父类PhoneDecorate的call方法,由于父类PhoneDecorate是抽象类不可能被实际创建对象,所以调用的是其new GuangGaoPhoneDecorate(new ColorPhoneDecorate(p))中ColorPhoneDecorate的call方法,于是被彩铃修饰的方法就被调用了,再加上广告功能的call方法,这个手机就被进行彩铃、广告双重修饰了,用一个简单的理解方法,pd = new GuangGaoPhoneDecorate(new ColorPhoneDecorate(p))就是将用彩铃装饰好的手机再用广告功能装饰一遍。
小结:当继承会使系统中产生大量难以管理的子类时,我们就可以考虑应用装饰模式了。装饰模式中大量进行了类与类之间的调用,尤其是子类、父类之间的构造方法调用,首先要明白,子类调用构造方法时时首先要调用父类的构造方法的;其次,装饰模式中使用了多态这一特性,都是我们需要好好理解的。