装饰模式
1.装饰模式的定义
动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。
装饰模式的通用类图如图所示:
1.1 使用场景
我们先不从定义或是类图中扯那么多没用的,先举个?。
接口:
interface People {
/**
* 灵魂
*/
fun mind()
/**
* 肉体
*/
fun body()
}
学生类:
class Student:People {
override fun mind() {
println("一个学生")
}
override fun body() {
println("穿上校服")
}
}
客户端:
class ClientActivity : Activity() {
fun init() {
//一个学生
val student: People = Student()
student.body()
student.mind()
}
}
输出结果:
这样看起来,还并没有什么不妥。
如果我们给一个场景呢?
场景一:描述10个灵魂和身体不同的学生。(可以看作10种不同的情况或场景)
你会选择继承,扩展子类的方式去实现吗?
- 不会。为什么?扩展的子类太多了,尤其是变化太多的时候。
那为什么我们选择了装饰模式呢?
- 10种变化,在最坏的情况下,我们也是会有10个装饰类的扩展。但是很多时候,10种变化,可能是几种不同的排列组合造成的结果,也许只需要4个装饰类呢?
- 装饰类并不影响被装饰的那个类,意味着灵活性非常高,等会可以看看我们写的代码。
场景二: Student类或许是一个final类,或是系统源码final类(人为不可修改)的。
你会选择继承,扩展子类的方式去实现吗?
- 不会,因为final限制了我们的子类扩展,不允许这样做。
但是我们可以选择代理模式吗?
- 当然可以。 但是还是得看更具体的场景使用,看自己的选择。
- 代理模式,更看重的是对被代理对象的一种控制。
- 而装饰模式,更看重的是添加或增强被装饰对象的功能。
场景三:两个Student对象,同一个方法实现的效果不同。
- 这种场景下,无论是同一个对象,还是多个对象,实现效果不同,都挺适合使用装饰模式的。就算你想用其他方式实现,我也不反对,就是那么圆滑。
1.2 代码实现
让我们来实现场景1下的代码:
这里就不写10种变化了,看你自己扩展吧。
接口:
interface People {
/**
* 灵魂
*/
fun mind()
/**
* 肉体
*/
fun body()
}
学生类:
class Student:People {
override fun mind() {
println("一个学生")
}
override fun body() {
println("穿上校服")
}
}
抽象装饰类:
abstract class Decorator:People{
var people:People?=null
}
具体装饰类:
/**
* 内心强大的装饰类
*/
class StrongStudent:Decorator() {
override fun mind() {
println("内心强大")
this.people?.mind()
}
override fun body() {
this.people?.body()
}
}
/**
* 生病穿多件外套的装饰类
*/
class IllStudent: Decorator() {
override fun mind() {
this.people?.mind()
}
override fun body() {
this.people?.body()
println("穿上校外套")
}
}
客户端:
class ClientActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
init()
}
fun init() {
//一个学生
val student: People = Student()
student.body()
student.mind()
println("===============")
//一个内心强大的学生
val strongStudent:Decorator = StrongStudent();
strongStudent.people = student
strongStudent.body()
strongStudent.mind()
println("===============")
//一个生病的学生
val illStudent:Decorator= IllStudent();
illStudent.people = student
illStudent.body()
illStudent.mind()
println("===============")
//一个生了病但内心强大的学生
illStudent.people = strongStudent
illStudent.body()
illStudent.mind()
}
}
输出结果
1.3 总结
经典实例:
Java的IO流(FileInputStream和BufferInputStream等等)
优点
- 灵活性高。
缺点
- 灵活性高。也正是灵活性太高,会导致一些问题。
(1) 比如,让组合逻辑暴露了出来,没有进行封装。至于到底封装不封装,看自己选择。
(2)装饰类大同小异,难以区分使用。好比一双休闲鞋和一双运动鞋,内行人知道在什么场合使用,但外行人只知道它们都是鞋。好比参与了需求分析的开发,知道如何使用。但是,后面维护的开发是懵的,需要付出一定的代价去理解。