Kotlin的装饰者模式与源码扩展

作者已经搬迁去隔壁网站,也欢迎大家关注我们的写作团队:天星技术团队。

闲聊

最近一直不在状态,月初就被博客质量的事给弄的情绪低落,之后群里又走了两个朋友,心情是一直在低谷徘徊,博客也是不想写,状态一天不如一天,总之就是一句话,不想工作。所以……
有没有小(fu)姐(luo)姐(li)私聊我啊!

设计模式刚入门的小伙伴可以先看看这篇《设计模式入门》,在文章末尾也将列出“设计模式系列”文章。欢迎大家关注留言投币丢香蕉。

什么是装饰者模式

为了方便理解,我们先举一些例子。
人是一个类,要上街的话,人就得穿衣服,裤子,鞋子,这是最简单的装扮,有些人还会打领带、背包、戴帽子等等。在这个例子中,“人”是一个被装饰者衣服裤子鞋子是装饰者。在整个打扮过程中,被装饰者类是不会被更改的,产生变化的是装饰者类的数量。
在吃火锅之前,我们会调料碗。那么空碗就是被装饰者,油、香菜、葱花就是装饰者;
点菜这一步骤,也是装饰者模式,鸳鸯锅就是被装饰者,麻辣牛肉、毛肚、鸭肠、菌肝、千层肚、鸭血、红糖糍粑就是装饰者;
装饰模式就是在不改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。
聊到这里,有没有一点饿?

Kotlin的装饰者模式与源码扩展_第1张图片

现在呢?

走进装饰者模式

首先看一下装饰者模式的UML图


Kotlin的装饰者模式与源码扩展_第2张图片

可以看出装饰模式中,有四个参与者:

  1. Component(抽象组件):定义对象的接口\抽象类,以规范准备接收附加责任的对象。
  2. ConcreteComponent(具体组件):具体的对象,抽象装饰者能给他新增职责
  3. Decorator(抽象装饰者):持有一个抽象组件对象的实例,并定义一个与抽象组件一致的接口。
  4. ConcreteDecorator(具体装饰者):具体的装饰对象。给内部持有的具体组件增加具体的职责;

我是按括号里的文字来记忆的,会比较容易记住。刚刚我们举例写到的“人”就是抽象组件;“男人\女人”就是具体组件;“装饰品”就是抽象组件;“帽子”就是具体组件;

装饰模式的特点

  1. 装饰者和被装饰者有相同的超类型
  2. 可以用一个或者多个装饰者包装一个对象
  3. 任何需要被装饰者对象的场合,可以用装饰过的对象代替它。(其实就是因为特点一)
  4. 装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为。(很重要)
  5. 对象可以在任何时候被装饰,包括运行时。

装饰模式的使用场合

  1. 在不影响其他对象的情况下,以动态、透明的方式给单个对象增加/撤销职责。
  2. 当不能采用生成子类的方法进行扩充时。

活生生的例子

我们先来分析一下上面提到的人化妆的例子。首先来创建四个参与者。
1.抽象组件Human ,给他一个自我介绍的描述,再添加一个方法,说出自己穿了什么。

abstract class Human {
    open var description = "I'm a human."
    abstract fun getDress() : String
}
  1. 继承抽象组件的具体组件Male\Female,改变下自我描述。
class Male : Human() {
    override var description: String
        get() = "我是男性"
        set(value) {}

    override fun getDress(): String {
        return "我穿了内裤"
    }
}
  1. 继承抽象组件的抽象装饰者Decoration
abstract class Decoration : Human() {
    abstract override var description: String
}
  1. 继承抽象装饰者的具体装饰者,这里我写一个帽子类,其他的随意添加。
class Hats : Decoration() {
    override var description: String
        get() = " 我有帽子"
        set(value) {}

    override fun getDress(): String {
        return "帽子"
    }
}

现在有了四个参与者,结构也按照UML写好了。那接下来就是包装了。
穿衣服时,我们会一件一件的穿(没有谁会同时穿吧?),所以,我们穿了裤子后,再穿衣服时,被装饰者是“男\女人+裤子”。看图!


Kotlin的装饰者模式与源码扩展_第3张图片

装饰者是一个一个去包裹被装饰者,这里要注意,衣服和裤子跟男人是同一个超类,我们在包裹的时候,需要把被包装的对象(被包装的对象可能是具体组件,也可能是已经被装饰者装饰之后的具体组件),传到装饰者中,所以我们需要在具体装饰者类中添加一个超类参数。这样才能得到被包装对象的所有参数。刚刚的具体装饰者类还没写完,补充完整应该是:

class Hats(var human: Human) : Decoration() {
    override var description: String
        get() = "帽子"
        set(value) {}

    override fun getDress(): String {
        return human.getDress() + " 帽子"
    }
}

再创造几个具体装饰者类,此时项目结构就是这样的。


Kotlin的装饰者模式与源码扩展_第4张图片

现在我们创造一个超人,给她穿上帽子,斗篷,并在界面中显示一下自己穿了些什么。

class MainActivity : AppCompatActivity() {
    var superMan : Human? = Female()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        superMan = Cloak(Hats(this.superMan!!))
        tv_textview.text = superMan?.description + superMan?.getDress()
    }
}

结果如下:


Kotlin的装饰者模式与源码扩展_第5张图片

总结一下

通过例子应该对装饰模式有个初步的理解了。再写demo的时候也只需要记住一下几步:

  1. 按照装饰模式UML图,写出四个参与者类。
  2. 在具体装饰者类的主构造函数中添加超类参数
  3. 父类引用指向子类对象创造具体组件
  4. 用具体装饰者装饰对象(需要什么装饰什么)

装饰模式优点:装饰模式比普通的继承更加灵活,能够在运行时更改组件的功能。而继承的类在运行前就决定了所有功能。
缺点:会创造很多的小类。别人看代码的时候会很脑壳痛。
注意:装饰模式中,装饰的顺序很重要。先穿“裤子”再穿“衣服”跟先穿“衣服”后穿“裤子”是不一样的

源码中的装饰模式Java I/O

我当初决定要学习设计模式的初衷,是为了看源码。现在我们就来一起来看看源码, 我也会顺便把我在这当中学到的一些看源码的方式方法说出来。大佬们请忽略这句话。
查看源码技巧一:查看它的父类子类并画图。
在AS右上角有个hierarchy按钮,点它可以查看当前类的直接父类和全部子类。没有这个按钮的话就按F4。

Kotlin的装饰者模式与源码扩展_第6张图片

java I/O 是比较庞大的一个库,如果直接看其代码,很难知道每个类都在干啥,也不知道它究竟怎么运作的。实话说,我以前就看晕了。
通过看它的类结构。我们能画出这样一张图:


Kotlin的装饰者模式与源码扩展_第7张图片

在这个设计中,InputStream就是抽象组件,FilterInputStream就是抽象装饰者, StringBufferInputStream、ByteArrayInputStream等是具体组件,LineNumberInputStream、DataInputStream、BufferedInputStream等是具体装饰者。
几个具体组件提供了不同类型的基本字节读取功能。
具体装饰类提供了额外的功能。例如:BufferedInputStream提供readline()方法。

源码扩展

接下来我们试试自编写一个新的具体装饰者类。

//将所有大写字符转为小写
class LowerCaseInputSteam(inputStream: InputStream) : FilterInputStream(inputStream){
    override fun read(): Int {
        val result = super.read()
        if(result==-1) return result
        else return Character.toLowerCase(result)
    }
    
    override fun read(b: ByteArray, off: Int, len: Int): Int {
        val  result = super.read(b, off, len)
        for (i in off until off+result){
            b[i] = Character.toLowerCase(b[i].toInt()).toByte()
        }
        return result
    }
}

此处需要实现两个方法,一个针对字节,一个针对字节组。

        try {
            var inputStream : InputStream =
                    LowerCaseInputSteam(BufferedInputStream(FileInputStream("手机文件路径")))
            c = inputStream.read()
            while (c!! >0) {
                stringBuffer?.append(c!!)
                c = inputStream.read()
            }
            tv_textview.text = stringBuffer.toString()
        }catch (e : IOException){
            e.printStackTrace()
        }

第一次写关于源码的东西,写的不好的地方,多多提意见。
下一次我将写代理模式,也会讲到装饰模式和代理模式的区别,和本章未提到的装饰模式的透明性。
以下是我“设计模式系列”文章,欢迎大家关注留言投币丢香蕉。
也可以进群跟大神们讨论。qq群:557247785

设计模式入门
Java与Kotlin的单例模式
Kotlin的装饰者模式与源码扩展
由浅到深了解工厂模式
为了学习Rxjava,年轻小伙竟作出这种事!

你可能感兴趣的:(Kotlin的装饰者模式与源码扩展)