装饰模式
动态的给一个对象添加一些额外的职责。就增加功能来说,Decorator模式现必生成子类更加灵活。–GOF 设计模式
该设计模式是让子类更加灵活,给某个对象而不是类添加功能。
坏代码
例如当前有一个流操作(Stream),分别有文件流(FileStream)、内存流(NetStream),而我们分别要对这些流进行编码(Ascii7)和压缩(Compressing) 或者还要编码和压缩一起搞,如果我们按照正常思维来写的话,将会产生很多很多的类,UML如下:
如果按照这种思维,以后再给流继续添加功能,有着更多功能的组合,那么就会产生更多的类,并随着日后代码的扩展,子类会不断的膨胀。这些类除了部分功能不同外,基本代码都极其相似。
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)
}
}
划分职责
此时,可以看到类Ascii7FileStream
和 Ascii7MemoryStream
的 read
操作中编码操作是相同的,唯一的不同仅仅是流的读取,一个是从文件读取,一个是从内存读取。产生了代码的冗余,不断的重复。而针对不同点 20 行和 27 行也不是完全的不同。
稍微进行一下代码的修改,将继承改为组合。
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
方法的不同点,此刻也成为了相同点。我们并没有修改了代码的功能,仍旧是原来的功能。而此刻 FileStream
和 MemoryStream
都有公共的父类,那么就可以接着进行修改,将编译时依赖,修改为运行时依赖。让代码在运行时在确定具体的类型(多态)。
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)
}
}
此时,Ascii7FIleStream
和 Ascii7MemoryStream
已经变得完全一样,此刻我们也不再需要两个类,只需要一个类 Ascii7Stream
。同理,针对压缩类也可以合并为一个类 CompressingStream
。
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
流的接口。
class Ascii7Stream(private var stream: Stream) : Stream {
override fun read(path: String) {
println("编码")
stream.read(path)
}
}
组合是为了更好的完成功能,将编译时依赖成为运行时依赖;而继承则是为第一点是符合 kotlin 语法、第二则是为了满足 Stream
的规范。
运行一下
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
简单运行一下,可以发现编码和压缩都完成了,那么编码加密操作呢?其实我们也已经实现了。
val fileStream = FileStream()
val ascii7Stream = Ascii7Stream(fileStream)
val compressingStream = CompressingStream(ascii7Stream)
compressingStream.read("456")
只需要将上一步完成的 Stream
在进行新的操作即可。这样意味着 CompressingAscii7...
这一系列的类就不需要再进行实现了。
现在再看我们的代码
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个类或者接口,无论函数还是类都相较于之前减少近一半,而且和之前完成的功能一样。
让代码变得更美一些
仔细观察代码,发现 CompressingStream
和 Ascii7Stream
都有公共的属性(stream
) ,我们可以将他们抽取到一个公共父类。
abstract class StreamDecorator(protected var stream: Stream) : Stream {
override fun read(path: String) {
stream.read(path)
}
}
新写一个 abstract 类,让 CompressingStream
和 Ascii7Stream
去继承新的 StreamDecorator
类。通过装饰类,将代码结构分割开来。以后扩展,只需要继承 StreaDecorator
即可。
相关内容
如果你觉得这篇文章对你有所帮助,欢迎赞赏~
赞赏