设计模式之装饰模式

问题的引出

人们日常生活中所使用的手机是不断迭代的、不断产生新功能的。初代的座机,只能打电话。如今手机经过了发展,又产生了很多功能,如听音乐、看视频等。

将手机当做一个产品类,如果我们要实现这一模型的话,若采用一般继承的方式来构造的话,无疑会产生很多类。如下:

interface ITelephone{
    void use();
}

class Telephone implements ITelephone{
    @Override
    public void use() {
        System.out.println("我能打电话");        
    }

}

class MusicPhone extends Telephone{
    public void use() {
        super.use();
        System.out.println("我能听音乐");
    }
}

class VideoPhone extends Telephone{
    public void use() {
        super.use();
        System.out.println("我能看视频");
    }
}

// ......

使用时,我们通过创建具体类来创建其对象

MusicPhone phone =new MusicPhone();
phone.use();

VideoPhone vphone = new VideoPhone();
vphone.use();

随着手机功能的增多,可以预见的是这些类会越来越多。更可怕的是,这些功能组合起来,又会产生更多的类,最后的结果就是类关系十分复杂。

但是采用装饰器模式就能避免这种复杂的子类问题。


装饰模式的作用:

  • 动态的为一个对象增加新的功能
  • 无需通过继承添加子类就能扩展对象的新功能,避免类型体系的快速膨胀

装饰模式中角色有:

  1. 抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。如上例中的ITelephone。
  2. 具体构件(Concrete Component)角色:定义一个将要接收附加责任的类。如上例中的Telephone。
  3. 装饰(Decorator)角色:持有一个构件(Component)对象的实例,并实现一个与抽象构件接口一致的接口。
  4. 具体装饰(Concrete Decorator)角色:负责给构件对象添加上附加的责任。

要实现一个装饰器,需要:

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

可以看出,装饰模式的实现与静态代理是十分相似的。都是要有一个共同的接口,然后相应功能类持有真实对象的引用。不同的是在装饰模式中,"装饰角色"中可以利用继承产生子类,子类中通过组合添加更强大的功能。

由此,我们将上述手机的模型修改如下
装饰模式

实现如下

// 抽象构件角色
interface ITelephone{
    void use();
}

// 具体构件角色
class Telephone implements ITelephone{
    @Override
    public void use() {
        System.out.println("我能打电话");        
    }
}

// 装饰角色
class CommonPhone implements ITelephone{
    protected ITelephone phone;
    public CommonPhone(ITelephone phone) {
        this.phone = phone;
    }
    
    @Override
    public void use() {
        phone.use();
    }
}

// 具体装饰角色
class MusicPhone extends CommonPhone{
    public MusicPhone(ITelephone phone) {
        super(phone);
    }
    
    public void use() {
        super.use();
        System.out.println("我能听音乐");
    }
}

// 具体装饰角色
class VideoPhone extends CommonPhone{

    public VideoPhone(ITelephone phone) {
        super(phone);
    }
    
    public void use() {
        super.use();
        System.out.println("我能看视频");
    }
}

这样每次我们要使用某个功能时,只需

//打电话
Telephone phone = new Telephone();
phone.use();
//打电话+听音乐
MusicPhone musicPhone = new MusicPhone(phone);
musicPhone.use();
//打电话+听音乐+看视频
VideoPhone videoPhone2 = new VideoPhone(musicPhone);
videoPhone2.use();

Java中的IO流的设计也正是采用装饰模式来实现的。