装饰者模式读书笔记

装饰者模式读书笔记

在平时写代码的过程中,大量的继承会造成继承滥用的现象,而我们可以使用组合(composition)和委托(delegation)在运行时具有继承的效果,通过动态的组合对象,可以写新的代码添加新的功能,而无需改变现有代码。既然没有改变现有代码,那么引进bug或者产生意外的副作用机会将大幅度减少。而装饰者模式恰恰是使用对象组合的方式,在运行时装饰类,从而避免使用大量的继承。

类应该对扩展开放,对修改关闭。 这就是开放-关闭原则

咖啡的例子

一杯咖啡由饮料(Beverage)还有各种调料组成,这些调料包括牛奶(milk),soy(豆浆),Mocha(摩卡),奶泡(whip)以及其他的一些调料。不同的调料和饮料进行组合就会构成不同种类的咖啡。而加上不同的调料,咖啡也就拥有不同的价格,而我们要做的就是为咖啡店做一个订单系统。

我们首先设计的类可能是这样的:
装饰者模式读书笔记_第1张图片

Beverage是一个抽象类,店内所提供的饮料必须继承自此类,cost()方法是抽象的,子类必须定义自己的实现。
购买咖啡时,我们可能要求要在里面加入各种调料,例如蒸奶(steamed Milk),豆浆(soy),或者覆盖奶泡。商店会根据所加入的调料不同而收取不同的费用。如果都是用继承的话,就会出现下面这种类爆炸的情况。

装饰者模式读书笔记_第2张图片

认识装饰者模式

如果利用继承的方式来解决上述的咖啡问题的话,我们会遇到很多问题,比如说类爆炸、设计死板、以及基类加入的新功能并不适用于所有的子类。

所以,我们用一种不同的方法,我们要以饮料为主体,然后再运行时以调料来“装饰”(decorate)饮料。比方说,如果顾客想要摩卡和奶泡深倍咖啡,那么我们要做的就是
1、以DarkRoast对象开始
装饰者模式读书笔记_第3张图片

2、顾客想要Mocha,所以建立一共Mocha对象,并用它将DarkRoast对象包(wrap)起来
装饰者模式读书笔记_第4张图片
3、顾客也想要奶泡(Whip),所以需要建立一共Whip装饰者,并用它将Mocha对象包起来
DarkRoast继承自Beverage,且有一个cost()方法,用来计算饮料价钱
装饰者模式读书笔记_第5张图片

4、现在要开始计算价格了,通过调用最外圈装饰者(Whip)的cost()就可以办到。Whip的cost()会先委托它装饰的对象(也就是Mocha)计算出价格。然后再加上奶泡的价格,以此类推。

定义装饰者

装饰者模式动态的将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

从上面我们可以看出:

  • 装饰者和被装饰者有相同的超类型
  • 你可以用一个或多个装饰者包装一个对象
  • 既然装饰者和被装饰者的对象有相同的超类型,所以在任何需要原始对象(被包装的)场合,可以用装饰过的对象代替他。
  • 装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定目的。

类图

装饰者模式读书笔记_第6张图片

装饰我们的饮料

装饰者模式读书笔记_第7张图片

左边是4个具体组件,每个代表一种咖啡类型,右边是调料装饰者。

代码

Beverage.kt

abstract class Beverage{

    var description = "Unknown Beverage";

    open fun getDes():String{
        return description
    }

    abstract fun cost():Double;
}

CondimentDecorator.kt

abstract class CondimentDecorator : Beverage(){
    override fun getDes():String{
        return description
    }
}

Espresso.kt

open class Espresso constructor(): Beverage(){
    init {
        description = "Espresso"
    }
    override fun cost(): Double {
        return 1.99
    }

    override fun getDes(): String {
        return description
    }
}

HouseBlend.kt

open class HouseBlend constructor(): Beverage(){
    init {
        description = "House Blend Coffee"
    }

    override fun getDes(): String {
        return description
    }


    override fun cost(): Double {
        return 0.89
    }
}

Mocha.kt

open class Mocha constructor(val beverage: Beverage) : CondimentDecorator(){
    override fun getDes(): String {
        return "${beverage.getDes()} , Mocha"
    }

    override fun cost(): Double {
        return .20 + beverage.cost()
    }
}

Whip.kt

open class Whip constructor(val beverage: Beverage): CondimentDecorator(){
    override fun cost(): Double {
        return 0.2 + beverage.cost()
    }

    override fun getDes(): String {
        return "${beverage.getDes()} , Whip"
    }
}

下面,我们就可以生产咖啡了。

fun main(args: Array){
    val beverage = Espresso()//一份Espresso咖啡
    println(beverage.description + "$ " + beverage.cost())

    val beverage4 = Whip(Mocha(Mocha(HouseBlend())))//加一份奶泡,两份摩卡的HouseBlend咖啡。
    println("${beverage4.cost()} $ ${beverage4.getDes()}")
}

下面是输出:

Espresso$ 1.99
1.49 $ House Blend Coffee , Mocha , Mocha , Whip

真实世界的装饰者:Java I/O

装饰者模式读书笔记_第8张图片

左边那些InputStream类是可以被装饰者包装起来的具体组件,还有一些类没有展示在这边

右边那些是具体的装饰者

总结

装饰者模式可以动态将责任附加到对象上,想要扩展功能,装饰者提供有别于继承的另一种选择。但是,装饰者也有一些缺点,比如说在使用装饰者实例化组件时,将增加代码的复杂度,一旦使用装饰者模式,不只需要实例化组件,还要把此组件包装进装饰者中,这可能有很多个。下面会讲到工厂模式和生成器模式,这两个模式对这个问题有很大的帮助。

你可能感兴趣的:(设计模式)