Java设计模式(三)装饰模式详解

一、引言

 

       在开发过程中经常遇到为已有模块添加新的功能或者对某个模块进行包装等操作。我们可以用继承来实现这一目的,但是更合适的途径是采用装饰设计模式对已有模块进行功能增强。装饰设计模式可以动态的为一些对象添加额外的职能,就增加功能来说,装饰设计模式比增加子类更加灵活(总结中会解释为什么)。本篇博文就将对装饰设计模式进行详细地介绍。


二、模式详解

 

2.1 模式分析

        装饰设计模式的思想是对对象进行逐层地包装,从图1所示的装饰设计模式UML图中可以看出,在装饰模式中有一个所有对象的父类抽象组件类Component,该类定义了所有对象的基本功能接口Opreation方法。Component有两个直接的子类,一个是实体组件类ConcretComponent,它定义的是一个已具备基本功能的实体对象;另一个是一个抽象装饰类Decorate,它维护了一个抽象组件类型的引用component,并重写了Opreation方法,在重写方法中调用component引用指向的实体组件类的基本功能,然后在此基础上添加新的功能(这一步实际上是由具体装饰类ConcreteDecorator最终实现的)。注意Decorator类和ConcreteComponent类是并列关系,而不是继承关系。


图-1 装饰模式UML图

2.2 举例说明

       我们用一个不同国家的人过周末的例子来举例说明装饰设计模式的作用。考虑用程序演示一个美国人和一个中国人的周末生活,其中美国人由于习俗周末不得不去教堂,而中国人则比较自由,周末可以和小伙伴愉快地玩耍。中国人和美国人起床后都要准备今天的衣服,美国人要穿西服打领带,中国人需要穿T恤穿运动鞋。

       分析上面的例子可以发现,中国人、美国人都是人,并且周末都有活动,因此我们可以抽象出一个Person抽象类,并且具有一个过周末的接口方法。

/**
 * 相当于抽象组件类,定义了人的基本功能接口:过周末
 */
abstract class Person {
    
    abstract public void weekend();
    
}/*Person*/

        接着定义一个中国人类和美国人类继承这个方法,并实现各自的基本功能,美国人去教堂,中国人找朋友,他们都相当于是具体组件类,已具备一个对象实例的基本功能。

/**
 * 相当于实体组件类,定义了中国人周末与朋友玩耍的行为接口
 */
class Chinese extends Person {

    public String name = null;
    
    public Chinese(String name) {
        this.name = name;
    }// constructor

    @Override
    public void weekend() {
        System.out.println(name + " is preparing to play with friend!");
    }// weekend
    
}/*Chinese*/

/**
 * 相当于实体组件类,定义了美国人周末去教堂的行为接口
 */
class American extends Person {
    
    public String name = null;
    
    public American(String name) {
        this.name = name; 
    }// constructor

    @Override
    public void weekend() {
        System.out.println(name + " is preparing to go to church!");
    }// weekend
    
}/*American*/

        中国人类和美国人类都值具备基本的过周末功能,现在要为其添加穿衣打扮的额外功能。这时,可以把每一种衣物都看作是一种装饰功能,这样我们就能抽象出一个衣物抽象类并继承Person类,它相当于UML中抽象装饰类,在该类中维护了一个Person类的引用,该引用指向了一个需要被装饰的具体组件类对象(Chiense或America),在重写的weekend方法中通过该引用调用具体组件类的基本功能。Clothes类之所以要继承Person类是因为即便进行了装饰,对象的根本属性不会改变,人穿了衣服还是人,不可能人穿了衣服之后就变成了非人。

/**
 * 相当于抽象装饰类,Clothes类为所有装饰功能类的抽象,之所以要继承Person类是因为即便进行
 * 了装饰,对象的根本属性不会改变,人穿了衣服还是人,不可能人穿了衣服之后就变成了非人。
 */
abstract class Clothes extends Person {
    
    protected Person person = null;

    @Override
    public void weekend() {
        person.weekend();
    }// weekend
        
}/*Decorate*/

      定义各种具体的装饰衣物类:T恤、运动鞋、西装和领带,这些具体装饰子类都继承自Clothes类,在重写的weekend方法中先调用具体组件类的基本功能然后在添加自身的特有功能。

/**
 * 相当于装饰实体类,继承自Clothes类,用于Person类对象增加新的功能:穿衬衣
 */
class T_Shirt extends Clothes {
    
    public T_Shirt(Person person) {
        this.person = person;
    }// constructor
    
    @Override
    public void weekend() {
        super.weekend();
        System.out.println("Wearing T-Shirt!");
    }// show
    
}/*T_Shirt*/

/**
 * 装饰实体类,继承自Clothes类,用于Person类对象增加新的功能:穿运动鞋
 */
class Sneaker extends Clothes {
    
    public Sneaker(Person person) {
        this.person = person;
    }// constructor
    
    @Override
    public void weekend() {
        super.weekend();
        System.out.println("Wearing sneaker!");
    }// show
    
}/*Sneaker*/

/**
 * 装饰实体类,继承自Clothes类,用于Person类对象增加新的功能:穿西服
 */
class Suit extends Clothes {
    
    public Suit(Person person) {
        this.person = person;
    }// constructor
    
    @Override
    public void weekend() {
        super.weekend();
        System.out.println("Wearing Suit!");
    }// show
    
}/*Suit*/

/**
 * 装饰实体类,继承自Clothes类,用于Person类对象增加新的功能:戴领带
 */
class Tie extends Clothes {
    
    public Tie(Person person) {
        this.person = person;
    }// constructor
    
    @Override
    public void weekend() {
        super.weekend();
        System.out.println("Wearing tie!");
    }// show
    
}/*Tie*/

      最后是客户端代码:

public class DecorateDemo {

    /**
     * 装饰模式对初始对象进行逐层的包装,在原功能的基础上不断增加新的功能,最终获得的对象不
     * 仅具有原始对象的基本特性,还获得了逐级添加的各装饰对象的特有功能。
     * @param args
     */
    public static void main(String[] args) {
        Person person1 = new American("Jack");  // 基本的美国人
        person1 = new Suit(person1);            // 穿西装的美国人
        person1 = new Tie(person1);             // 穿西装打领带的美国人
        person1.weekend();                      // 最终的美国人的展示
        
        System.out.println();
        Person person2 = new Chinese("Li Si");  // 基本的中国人
        person2 = new T_Shirt(person2);         // 穿衬衣的中国人
        person2 = new Sneaker(person2);         // 穿衬衣穿运动鞋的中国人
        person2.weekend();                      // 最终的中国人的展示
    }// main

}/*DecoratePatternDemo*/

运行结果:



三、总结

        

        在之前的引言中我们就说对增加对象功能来说,使用装饰模式比使用继承增加子类要灵活的多,下面我们就来分析一下为什么。

       还是考虑上面的例子,假设我们使用继承方式来实现穿着打扮功能的添加,那么由于之前已经将人分为了美国人和中国人两个分支,如果要增加穿衣打扮的功能,那么我们分别要在美国人下和中国人下都添加四个衣服子类(虽然在本例中美国人和中国人分别选了两种不同的衣服,但在其他场合可能互换,因此四个子类都要派生),此时的代码结构为:

|----人

    |----美国人

        |----西装

        |----领带

        |----T恤

        |----运动鞋

    |----中国人

        |----西装

        |----领带

        |----T恤

        |----运动鞋

       按照自然常识,不管是美国人还是中国人,穿衣服的方式都是一样的,但是在继承体系中每一种穿衣方式都被被实现了两次,造成代码的冗余和体系的繁杂。而在装饰者模式中的代码结构为:

|----人

    |----美国人

    |----中国人

    |----衣物类

        |----西装

        |----领带

        |----T恤

        |----运动鞋

       可以看出,装饰模式的继承体系要比使用增加子类的方法的体系简约的多,这就是装饰模式的优点。

       装饰设计模式的作用在于可以提高企装饰功能的附加类的复用率,减少代码的重复率,最直接的应用在于IO流上。由于打开不同类型的文件需要不同的流,比如打开文本文件可以使用文件流,打开媒体文件需要媒体流,而这些刘都需要添加附加功能——缓冲区,如果使用继承体系创建缓冲区则需要为文本流和媒体流都创建一个缓冲区类,由于文本流和媒体流是并列关系,如果使用继承模式将会出现一个FileBuffer和一个MediemBuffer,这两个Buffer之间难免存在重复的代码,从而造成体系的冗余。如果使用装饰模式,我们从FileStream和MediumStream中抽象出一个Stream类,将缓冲区作为装饰功能类,从而构造出一个BufferedStream类,该类含有一个Stream类引用类型的成员变量,这样可以将Medium和FileStream类传给该成员变量,就可实现流缓冲区的功能,而体系中仅有一个BufferedStream类。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值