在平时写代码的过程中,大量的继承会造成继承滥用的现象,而我们可以使用组合(composition)和委托(delegation)在运行时具有继承的效果,通过动态的组合对象,可以写新的代码添加新的功能,而无需改变现有代码。既然没有改变现有代码,那么引进bug或者产生意外的副作用机会将大幅度减少。而装饰者模式恰恰是使用对象组合的方式,在运行时装饰类,从而避免使用大量的继承。
类应该对扩展开放,对修改关闭。 这就是开放-关闭原则
一杯咖啡由饮料(Beverage)还有各种调料组成,这些调料包括牛奶(milk),soy(豆浆),Mocha(摩卡),奶泡(whip)以及其他的一些调料。不同的调料和饮料进行组合就会构成不同种类的咖啡。而加上不同的调料,咖啡也就拥有不同的价格,而我们要做的就是为咖啡店做一个订单系统。
Beverage是一个抽象类,店内所提供的饮料必须继承自此类,cost()方法是抽象的,子类必须定义自己的实现。
购买咖啡时,我们可能要求要在里面加入各种调料,例如蒸奶(steamed Milk),豆浆(soy),或者覆盖奶泡。商店会根据所加入的调料不同而收取不同的费用。如果都是用继承的话,就会出现下面这种类爆炸的情况。
如果利用继承的方式来解决上述的咖啡问题的话,我们会遇到很多问题,比如说类爆炸、设计死板、以及基类加入的新功能并不适用于所有的子类。
所以,我们用一种不同的方法,我们要以饮料为主体,然后再运行时以调料来“装饰”(decorate)饮料。比方说,如果顾客想要摩卡和奶泡深倍咖啡,那么我们要做的就是
1、以DarkRoast对象开始
2、顾客想要Mocha,所以建立一共Mocha对象,并用它将DarkRoast对象包(wrap)起来
3、顾客也想要奶泡(Whip),所以需要建立一共Whip装饰者,并用它将Mocha对象包起来
DarkRoast继承自Beverage,且有一个cost()方法,用来计算饮料价钱
4、现在要开始计算价格了,通过调用最外圈装饰者(Whip)的cost()就可以办到。Whip的cost()会先委托它装饰的对象(也就是Mocha)计算出价格。然后再加上奶泡的价格,以此类推。
装饰者模式动态的将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
从上面我们可以看出:
左边是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
左边那些InputStream类是可以被装饰者包装起来的具体组件,还有一些类没有展示在这边
右边那些是具体的装饰者
装饰者模式可以动态将责任附加到对象上,想要扩展功能,装饰者提供有别于继承的另一种选择。但是,装饰者也有一些缺点,比如说在使用装饰者实例化组件时,将增加代码的复杂度,一旦使用装饰者模式,不只需要实例化组件,还要把此组件包装进装饰者中,这可能有很多个。下面会讲到工厂模式和生成器模式,这两个模式对这个问题有很大的帮助。