动态的给一个对象添加一些额外的职责。就增加功能来说,Decorator模式现必生成子类更加灵活。–GOF 设计模式

该设计模式是让子类更加灵活,给某个对象而不是添加功能。

坏代码

例如当前有一个流操作(Stream),分别有文件流(FileStream)、内存流(NetStream),而我们分别要对这些流进行编码(Ascii7)和压缩(Compressing) 或者还要编码和压缩一起搞,如果我们按照正常思维来写的话,将会产生很多很多的类,UML如下:

如果按照这种思维,以后再给流继续添加功能,有着更多功能的组合,那么就会产生更多的类,并随着日后代码的扩展,子类会不断的膨胀。这些类除了部分功能不同外,基本代码都极其相似。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
interface Stream {
    fun read(path: String)
}

open class FileStream : Stream {
    override fun read(path: String) {
        println("FileStream.read $path")
    }
}

open class MemoryStream : Stream {
    override fun read(path: String) {
        println("MemoryStream.read $path")
    }
}

class Ascii7FileStream : FileStream() {
    override fun read(path: String) {
        println("编码")
        super.read(path)
    }
}

class Ascii7MemoryStream : MemoryStream() {
    override fun read(path: String) {
        println("编码")
        super.read(path)
    }
}

class CompressingFileStream : FileStream() {
    override fun read(path: String) {
        println("压缩")
        super.read(path)
    }
}

class CompressingMemoryStream : MemoryStream() {
    override fun read(path: String) {
        println("压缩")
        super.read(path)
    }
}

class CompressingAscii7FileStream : FileStream() {
    override fun read(path: String) {
        println("压缩")
        println("编码")
        super.read(path)
    }
}

class CompressingAscii7MemoryStream : MemoryStream() {
    override fun read(path: String) {
        println("压缩")
        println("编码")
        super.read(path)
    }
}

划分职责

此时,可以看到类Ascii7FileStreamAscii7MemoryStreamread 操作中编码操作是相同的,唯一的不同仅仅是流的读取,一个是从文件读取,一个是从内存读取。产生了代码的冗余,不断的重复。而针对不同点 20 行和 27 行也不是完全的不同。

稍微进行一下代码的修改,将继承改为组合。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class Ascii7FileStream {
    private lateinit var stream: FileStream

    override fun read(path: String) {
        println("编码")
        stream.read(path)
    }
}

class Ascii7MemoryStream {
    private lateinit var stream: MemoryStream

    override fun read(path: String) {
        println("编码")
        stream.read(path)
    }
}

经过修改的代码,发现针对 read 方法的不同点,此刻也成为了相同点。我们并没有修改了代码的功能,仍旧是原来的功能。而此刻 FileStreamMemoryStream 都有公共的父类,那么就可以接着进行修改,将编译时依赖,修改为运行时依赖。让代码在运行时在确定具体的类型(多态)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Ascii7FileStream(private var stream: Stream) {

    override fun read(path: String) {
        println("编码")
        stream.read(path)
    }
}

class Ascii7MemoryStream(private var stream: Stream) {

    override fun read(path: String) {
        println("编码")
        stream.read(path)
    }
}

此时,Ascii7FIleStreamAscii7MemoryStream 已经变得完全一样,此刻我们也不再需要两个类,只需要一个类 Ascii7Stream。同理,针对压缩类也可以合并为一个类 CompressingStream

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Ascii7Stream(private var stream: Stream) {

    override fun read(path: String) {
        println("编码")
        stream.read(path)
    }
}

class CompressingStream(private var stream: Stream) {

    override fun read(path: String) {
        println("压缩")
        stream.read(path)
    }
}

组合加继承

此时,细心的朋友肯定发现了,你既没有继承父类或者抽象类,也实现接口,你怎么方法前面还是 override 啊!是啊,这个情况在 kotlin 上是语法错误的。所以就要把 override 去掉吗?当然也不可以,我们既然是实现 Stream 的功能,那就意味着要遵循 Stream 的规范。仍旧需要去实现 Stream 流的接口。

1
2
3
4
5
6
7
class Ascii7Stream(private var stream: Stream) : Stream {

    override fun read(path: String) {
        println("编码")
        stream.read(path)
    }
}

组合是为了更好的完成功能,将编译时依赖成为运行时依赖;而继承则是为第一点是符合 kotlin 语法、第二则是为了满足 Stream 的规范。

运行一下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fun main() {
    val fileStream = FileStream()
    val ascii7Stream = Ascii7Stream(fileStream)
    ascii7Stream.read("123")
    val memoryStream = MemoryStream()
    val compressingStream = CompressingStream(memoryStream)
    compressingStream.read("456")
}
// 编码
// FileStream.read 123
// 压缩
// MemoryStream.read 456

简单运行一下,可以发现编码和压缩都完成了,那么编码加密操作呢?其实我们也已经实现了。

1
2
3
4
    val fileStream = FileStream()
    val ascii7Stream = Ascii7Stream(fileStream)
    val compressingStream = CompressingStream(ascii7Stream)
    compressingStream.read("456")

只需要将上一步完成的 Stream 在进行新的操作即可。这样意味着 CompressingAscii7...这一系列的类就不需要再进行实现了。

现在再看我们的代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
interface Stream {
    fun read(path: String)
}

open class FileStream : Stream {
    override fun read(path: String) {
        println("FileStream.read $path")
    }
}

open class MemoryStream : Stream {
    override fun read(path: String) {
        println("MemoryStream.read $path")
    }
}

class Ascii7Stream(private var stream: Stream) : Stream {

    override fun read(path: String) {
        println("编码")
        stream.read(path)
    }
}

class CompressingStream(private var stream: Stream) : Stream {

    override fun read(path: String) {
        println("压缩")
        stream.read(path)
    }
}

相比于第一个版本的代码 59 行,9个类或者接口,而现在我们仅仅需要31行,5个类或者接口,无论函数还是类都相较于之前减少近一半,而且和之前完成的功能一样。

让代码变得更美一些

仔细观察代码,发现 CompressingStreamAscii7Stream 都有公共的属性(stream) ,我们可以将他们抽取到一个公共父类。

1
2
3
4
5
abstract class StreamDecorator(protected var stream: Stream) : Stream {
    override fun read(path: String) {
        stream.read(path)
    }
}

新写一个 abstract 类,让 CompressingStreamAscii7Stream 去继承新的 StreamDecorator 类。通过装饰类,将代码结构分割开来。以后扩展,只需要继承 StreaDecorator 即可。