设计模式之装饰者模式

概述
装饰者模式是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。


特点
(1).装饰者和被装饰者对象有相同的超类型。
(2).你可以用一个或多个装饰者包装一个对象。
(3).既然装饰者和被装饰者对象有相同的超类型,所以在任何需要原始对象(被包装的)的场合,可以用装饰多的对象代替它。
(4).装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的。
(5).对象可以在任何时候被装饰,所以可以在运行时动态的、不限量的用你喜欢的装饰者来装饰对象。


设计原则
1. 多用组合,少用继承。
利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。然而,如果能够利用组合的做法扩展对象的行为,就可以在运行时动态地进行扩展。
2.类应该对扩展开放,对修改关闭。


类图



代码示例
案例来自Head First,星巴兹咖啡的订单系统问题:购买咖啡时,可以要求在其中加入各种调料,例如豆浆、摩卡等,星巴兹会根据所加入的调料收取不同的费用。

先从Beverage类下手,如下所示:

// Beverage是一个抽象类,有两个方法:getDescription()和cost()。
public abstract class Beverage {
    String description = "Unknow Beverage";

    // getDescription()已经实现了
    public String getDescription() {
        return description;
    }

    // cost()必须在子类中实现
    public abstract double cost();
}


Beverage类很简单。让我们来实现Condiment(调料)抽象类,也就是装饰者类吧:
// 首先,必须让CondimentDecorator能够取代Beverage,所以将CondimentDecorator扩展自Beverage类。
public abstract class CondimentDecorator extends Beverage {
    // 所有的调料装饰者都必须重新实现getDescription()方法。
    public abstract String getDescription();
}

饮料的代码
现在,已经有了基类,我们开始实现一些饮料。先从浓缩咖啡(Espresso)开始。别忘了,我们需要为具体的饮料设置描述,而且还必须实现cost()方法。

// 首先,让Espresso扩展自Beverage类,因为Espresso是一种饮料。
public class Espresso extends Beverage {
    // 为了要设置饮料的描述,我们写了一个构造器。记住,description实例变量继承自Beverage类。
    public Espresso() {
        description = "Espresso";
    }

    // 最后,需要计算Espresso的价钱,现在不需要管调料的价钱,直接把Espresso的价格1.99返回即可。
    public double cost() {
        return 1.99;
    }
}

// 这是另一种饮料,做法和Espresso一样,只是把Espresso名称改为HouseBlend,并返回价钱0.89。
public class HouseBlend extends Beverage {
    public Espresso() {
        description = "Espresso";
    }

    // 最后,需要计算Espresso的价钱,现在不需要管调料的价钱,直接把Espresso的价格1.99返回即可。
    public double cost() {
        return 0.89;
    }
}

// 你可以自行建立另外的饮料类,做法都一样。

调料的代码
如果回头去看看装饰者模式的类图,将发现我们已经完成了抽象组件(Beverage),有了具体的组件(HouseBlend),也有了抽象装饰者(CondimentDecorator)。现在,我们就来实现具体的装饰者。先从摩卡下手:

// Mocha是一个装饰者,所以让它扩展自CondimentDecorator。别忘了,CondimentDecorator扩展自Beverage。
public class Mocha extends CondimentDecorator {
    Beverage beverage;

    // 要让Mocha能够引用一个Beverage,做法如下:
    // (1)用一个实例变量记录饮料,也就是被装饰者。
    // (2)想办法让被装饰者(饮料)被记录到实例变量中。这里的做法是,把饮料当做构造器的参数,再由构造器将此饮料记录在实例变量中。
    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }

    // 我们希望description不只是描述饮料(例如“DarkRoast”),而是完整地连调料都描述出来(例如“DarkRoast, Mocha”)。
    // 所以首先利用委托的做法,得到一个描述,然后在其后加上附加的描述(例如“Mocha”)。
    public String getDescription() {
        return beverage.getDescription() + ", Mocha";
    }

    // 要计算贷Mocha饮料的价钱,首先把调用委托给被装饰对象,以计算价钱,然后再加上Mocha的价钱,得到最后结果。
    public double cost() {
        return 0.20 + beverage.cost();
    }
}

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

	// 制造出一个DarkRoast对象。
        Beverage beverage2 = new DarkRoast();
	// 用Mocha装饰它
	beverage2 = new Mocha(beverage2);
	// 用第二个Mocha装饰它
	beverage2 = new Mocha(beverage2);
	// 用Whip装饰它
	beverage2 = new Whip(beverage2);
	System.out.println(beverage2.getDescription + " $" + beverage2.cost());

	// 最后,再来一杯调料为豆浆,摩卡,奶泡的HouseBlend咖啡。
        Beverage beverage3 = new HouseBlend();
	beverage3 = new Soy(beverage3);
	beverage3 = new Mocha(beverage3);
	beverage3 = new Whip(beverage3);
	System.out.println(beverage3.getDescription + " $" + beverage3.cost());
    }
}

典型案例
java IO相关的类。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值