sheng的学习笔记-设计模式-装饰模式

原理图:

装饰器(Decorator)模式的定义:指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。

 

在 OO 设计和开发过程, 可能会经常遇到以下的情况: 我们需要为一个已经定义好的类
添加新的职责(操作), 通常的情况我们会给定义一个新类继承自定义好的类,这样会带来
一个问题( 将在本模式的讨论中给出)。通过继承的方式解决这样的情况还带来了系统的复
杂性,因为继承的深度会变得很深。
而 Decorator 提供了一种给类增加职责的方法,不是通过继承实现的,而是通过组合。
 

在 结 构 图 中 , ConcreteComponent 和 Decorator 需 要 有 同 样 的 接 口 , 因 此
ConcreteComponent 和 Decorator 有着一个共同的父类。这里有人会问,让 Decorator 直接维
护一个指向 ConcreteComponent 引用(指针) 不就可以达到同样的效果, 答案是肯定并且是
否定的。 肯定的是你可以通过这种方式实现, 否定的是你不要用这种方式实现, 因为通过这
种方式你就只能为这个特定的 ConcreteComponent 提供修饰操作了,当有了一个新的ConcreteComponent 你 又 要 去 新 建 一 个 Decorator 来 实 现 。 但 是 通 过 结 构 图 中 的
ConcreteComponent 和 Decorator 有一个公共基类, 就可以利用 OO 中多态的思想来实现只要
是 Component 型别的对象都可以提供修饰操作的类,这种情况下你就算新建了 100 个
Component 型别的类 ConcreteComponent,也都可以由 Decorator 一个类搞定。这也正是
Decorator 模式的关键和威力所在了。
                                                                                                        --摘自 二十三种设计模式 

Decorator 模式和 Proxy 模式的相似的地方在于它们都拥有一个指向其他对象的引用(指
针), 即通过组合的方式来为对象提供更多操作(或者 Decorator 模式) 间接性( Proxy 模式)。
但是他们的区别是, Proxy 模式会提供使用其作为代理的对象一样接口, 使用代理类将其操
作都委托给 Proxy 直接进行。这里可以简单理解为组合和委托之间的微妙的区别了。
Decorator 模式除了采用组合的方式取得了比采用继承方式更好的效果, Decorator 模式
还给设计带来一种“即用即付” 的方式来添加职责。 在 OO 设计和分析经常有这样一种情况:
为了多态, 通过父类指针指向其具体子类, 但是这就带来另外一个问题, 当具体子类要添加
新的职责, 就必须向其父类添加一个这个职责的抽象接口, 否则是通过父类指针是调用不到
这个方法了。这样处于高层的父类就承载了太多的特征( 方法),并且继承自这个父类的所
有子类都不可避免继承了父类的这些接口, 但是可能这并不是这个具体子类所需要的。 而在
Decorator 模式提供了一种较好的解决方法, 当需要添加一个操作的时候就可以通过
Decorator 模式来解决,你可以一步步添加新的职责。
                                                                                                        --摘自 二十三种设计模式

代码示例如下:

package design.decorator;

//抽象构件角色
interface Component {
    public void operation();
}

//装饰模式 :指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。
public class DecoratorPattern {
    public static void main(String[] args) {
        Component p = new ConcreteComponent();
        System.out.println("---------------------------------");
        ConcreteDecorator1 d = new ConcreteDecorator1(p);
        ConcreteDecorator2 d2 = new ConcreteDecorator2(d);
        d2.operation();
    }
}

//抽象装饰角色
abstract class Decorator implements Component {
    public abstract void operation();
}

//具体构件角色
class ConcreteComponent implements Component {
    public ConcreteComponent() {
        System.out.println("创建具体构件角色");
    }

    public void operation() {
        System.out.println("调用具体构件角色的方法operation()");
    }
}

//具体装饰角色
class ConcreteDecorator1 extends Decorator {
    Component component;

    public ConcreteDecorator1(Component component) {
        this.component = component;
    }

    @Override
    public void operation() {
        component.operation();
        addedFunction();
    }

    public void addedFunction() {
        System.out.println("ConcreteDecorator1为具体构件角色增加额外的功能addedFunction()");
    }
}


//具体装饰角色
class ConcreteDecorator2 extends Decorator {
    Component component;

    public ConcreteDecorator2(Component component) {
        this.component = component;
    }

    @Override
    public void operation() {
        component.operation();
        addedFunction();
    }

    public void addedFunction() {
        System.out.println("ConcreteDecorator2为具体构件角色增加额外的功能addedFunction()");
    }
}

运行结果:

创建具体构件角色
---------------------------------
调用具体构件角色的方法operation()
ConcreteDecorator1为具体构件角色增加额外的功能addedFunction()
ConcreteDecorator2为具体构件角色增加额外的功能addedFunction()

扩展示例:咖啡设计

现在呢,有一个咖啡馆,它有一套自己的订单系统,当顾客来咖啡馆的时候,可以通过订单系统来点自己想要的咖啡。他们原先的设计是这样子的:

 

此时、咖啡馆为了吸引更多的顾客,需要在订单系统中允许顾客选择加入不同调料的咖啡,例如:蒸奶(Steamed Milk)、豆浆(Soy)、摩卡(Mocha,也就是巧克力风味)或覆盖奶泡。星巴兹会根据所加入的调料收取不同的费用。所以订单系统必须考虑到这些调料部分。

  下面是他们的第一次尝试:

这种设计肯定是不行的,简直分分钟把人逼疯的节奏,有木有!

  3、这时,有个人提出了新的方案,利用实例变量和继承,来追踪这些调料。

    具体为:先从Beverage基类下手,加上实例变量代表是否加上调料(牛奶、豆浆、摩卡、奶泡……),

这种设计虽然满足了现在的需求,但是我们想一下,如果出现下面情况,我们怎么办,

    ①、调料价钱的改变会使我们更改现有代码。

    ②、一旦出现新的调料,我们就需要加上新的方法,并改变超类中的cost()方法。

    ③、以后可能会开发出新饮料。对这些饮料而言(例如:冰茶),某些调料可能并不适合,但是在这个设计方式中,Tea(茶)子类仍将继承那些不适合的方法,例如:hasWhip()(加奶泡)。

    ④、万一顾客想要双倍摩卡咖啡,怎么办?

  很明显,上面的设计并不能够从根本上解决我们所碰到的问题。并且这种设计违反了 开放关闭原则(类应该对扩展开放,对修改关闭。)。

  那我们怎么办呢?好啦,装饰者可以非常完美的解决以上的所有问题,让我们有一个设计非常nice的咖啡馆。

扩展示例:咖啡设计实现

 

下面我们就利用装饰者模式来实现一个全新的咖啡馆。

  1、我们要以饮料为主体,然后在运行时以调料来“装饰”(decorate)饮料。比方说,如果顾客想要摩卡和奶泡深焙咖啡,那么,要做的是:

    ①、拿一个深焙咖啡(DarkRoast)对象

    ②、以摩卡(Mocha)对象装饰它

    ③、以奶泡(Whip)对象装饰它

    ④、调用cost()方法,并依赖委托(delegate)将调料的价钱加上去。

  好了!但是如何“装饰”一个对象,而“委托”又要如何与此搭配使用呢?那就是把装饰者对象当成“包装者”。让我们看看这是如何工作的:

 

我们将我们所知道的写下来:

    ①、装饰者和被装饰对象有相同的超类型。

    ②、你可以用一个或多个装饰者包装一个对象。

    ③、既然装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装的)的场合,可以用装饰过的对象代替它。

    ④、装饰者可以在所委托被装饰者的行为之前与 / 或之后,加上自己的行为,以达到特定的目的。

    ⑤、对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象。

  下面,我们来看一下装饰者模式的类图:

 

package design.decorator;

import java.math.BigDecimal;


/**
 * 我们要以饮料为主体,然后在运行时以调料来“装饰”(decorate)饮料。比方说,如果顾客想要摩卡和奶泡深焙咖啡,那么,要做的是:
 * <p>
 *     ①、拿一个深焙咖啡(DarkRoast)对象
 * <p>
 *     ②、以摩卡(Mocha)对象装饰它
 * <p>
 *     ③、以奶泡(Whip)对象装饰它
 * <p>
 *     ④、调用cost()方法,并依赖委托(delegate)将调料的价钱加上去。
 */

/**
 * 咖啡馆(供应咖啡)
 */
public class BeverageDecorator {

    public static void main(String[] args) {
        //订一杯Espresso(2.00),不需要调料,打印出它的描述与价钱。
        Beverage beverage = new Espresso();
        System.out.println("Description: " + beverage.getDescription() + " $" + beverage.cost());

        //制造出一个DarkRoast(3.00)对象,用Mocha(0.2)装饰它,用第二个Mocha(0.2)装饰它,用Whip(0.4)装饰它,打印出它的描述与价钱。
        Beverage beverage2 = new DarkRoast();
        beverage2 = new Mocha(beverage2);
        beverage2 = new Mocha(beverage2);
        beverage2 = new Whip(beverage2);
        System.out.println("Description: " + beverage2.getDescription() + " $" + beverage2.cost());

        //再来一杯调料为豆浆(Soy 0.3)、摩卡(Mocha 0.2)、奶泡(Whip 0.4)的Decaf(低咖啡因咖啡 4.00),打印出它的描述与价钱。
        Beverage beverage3 = new Decaf();
        beverage3 = new Soy(beverage3);
        beverage3 = new Mocha(beverage3);
        beverage3 = new Whip(beverage3);
        System.out.println("Description: " + beverage3.getDescription() + " $" + beverage3.cost());
    }
}

// 饮料抽象类
abstract class Beverage {

    String description = "Unknown Beverage";

    public String getDescription() {
        return description;
    }

    /**
     * cost方法是用来返回饮料的价钱(需在具体类中自己实现)
     *
     * @return
     */
    public abstract BigDecimal cost();
}

/**
 * 深焙咖啡类(一种具体的饮料)
 */
class DarkRoast extends Beverage {

    /**
     * 说明他是DarkRoast饮料
     */
    public DarkRoast() {
        description = "DarkRoast";
    }

    /**
     * 实现cost方法,用来返回DarkRoast(深焙咖啡)的价格
     *
     * @return
     */
    @Override
    public BigDecimal cost() {
        return new BigDecimal("3.00");
    }
}

/**
 * 低咖啡因咖啡类(一种具体的饮料)
 */
class Decaf extends Beverage {

    /**
     * 说明他是Decaf饮料
     */
    public Decaf() {
        description = "Decaf";
    }

    /**
     * 实现cost方法,用来返回Decaf(低咖啡因咖啡)的价格
     *
     * @return
     */
    @Override
    public BigDecimal cost() {
        return new BigDecimal("4.00");
    }
}

/**
 * 浓缩咖啡类(一种具体饮料)
 */
class Espresso extends Beverage {

    /**
     * 说明他是Espresso饮料
     */
    public Espresso() {
        description = "Espresso";
    }

    /**
     * 实现cost方法,用来返回Espresso(浓缩咖啡)的价格
     *
     * @return
     */
    @Override
    public BigDecimal cost() {
        return new BigDecimal("2.00");
    }
}

//调料装饰着抽象类(继承自饮料抽象类)
abstract class CondimentDecorator extends Beverage {

    /**
     * 所有的调料装饰者都必须重新实现getDescription()方法
     * 这样才能够用递归的方式来得到所选饮料的整体描述
     *
     * @return
     */
    public abstract String getDescription();
}

/**
 * 摩卡调料类(继承自CondimentDecorator)
 */
class Mocha extends CondimentDecorator {

    /**
     * 用一个实例变量记录饮料,也就是被装饰者
     */
    Beverage beverage;

    /**
     * 构造器初始化饮料变量
     *
     * @param beverage
     */
    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }

    /**
     * 在原来饮料的基础上添加上Mocha描述(原来的饮料加入Mocha调料,被Mocha调料装饰)
     *
     * @return
     */
    @Override
    public String getDescription() {
        return beverage.getDescription() + ",Mocha";
    }

    /**
     * 在原来饮料的基础上加上Mocha的价格(原来的饮料加入Mocha调料,被Mocha调料装饰)
     *
     * @return
     */
    @Override
    public BigDecimal cost() {
        return new BigDecimal("0.2").add(beverage.cost());
    }
}

/**
 * 豆浆调料类(继承自CondimentDecorator))
 */
class Soy extends CondimentDecorator {

    /**
     * 用一个实例变量记录饮料,也就是被装饰者
     */
    Beverage beverage;

    /**
     * 构造器初始化饮料变量
     *
     * @param beverage
     */
    public Soy(Beverage beverage) {
        this.beverage = beverage;
    }

    /**
     * 在原来饮料的基础上添加上Soy描述(原来的饮料加入Soy调料,被Soy调料装饰)
     *
     * @return
     */
    @Override
    public String getDescription() {
        return beverage.getDescription() + ",Soy";
    }

    /**
     * 在原来饮料的基础上加上Soy的价格(原来的饮料加入Soy调料,被Soy调料装饰)
     *
     * @return
     */
    @Override
    public BigDecimal cost() {
        return new BigDecimal("0.3").add(beverage.cost());
    }
}

/**
 * 奶泡调料类(继承自CondimentDecorator)
 */
class Whip extends CondimentDecorator {

    /**
     * 用一个实例变量记录饮料,也就是被装饰者
     */
    Beverage beverage;

    /**
     * 构造器初始化饮料变量
     *
     * @param beverage
     */
    public Whip(Beverage beverage) {
        this.beverage = beverage;
    }

    /**
     * 在原来饮料的基础上添加上Whip描述(原来的饮料加入Whip调料,被Whip调料装饰)
     *
     * @return
     */
    @Override
    public String getDescription() {
        return beverage.getDescription() + ",Whip";
    }

    /**
     * 在原来饮料的基础上加上Whip的价格(原来的饮料加入Whip调料,被Whip调料装饰)
     *
     * @return
     */
    @Override
    public BigDecimal cost() {
        return new BigDecimal("0.4").add(beverage.cost());
    }
}



 运行结果:

Description: Espresso $2.00
Description: DarkRoast,Mocha,Mocha,Whip $3.80
Description: Decaf,Soy,Mocha,Whip $4.90

 

优缺点:

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

2、缺点:多层装饰比较复杂。

JAVA IO使用的装饰模式:

文章参考:

装饰器模式(装饰设计模式)详解 

咖啡的示例完全来源于:

设计模式之装饰者模式 - 心中的山水 - 博客园

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值