作者已经搬迁去隔壁网站,也欢迎大家关注我们的写作团队:天星技术团队。
闲聊
最近一直不在状态,月初就被博客质量的事给弄的情绪低落,之后群里又走了两个朋友,心情是一直在低谷徘徊,博客也是不想写,状态一天不如一天,总之就是一句话,不想工作。所以……
有没有小(fu)姐(luo)姐(li)私聊我啊!
设计模式刚入门的小伙伴可以先看看这篇《设计模式入门》,在文章末尾也将列出“设计模式系列”文章。欢迎大家关注留言投币丢香蕉。
什么是装饰者模式
为了方便理解,我们先举一些例子。
人是一个类,要上街的话,人就得穿衣服,裤子,鞋子,这是最简单的装扮,有些人还会打领带、背包、戴帽子等等。在这个例子中,“人”是一个被装饰者,衣服裤子鞋子是装饰者。在整个打扮过程中,被装饰者类是不会被更改的,产生变化的是装饰者类的数量。
在吃火锅之前,我们会调料碗。那么空碗就是被装饰者,油、香菜、葱花就是装饰者;
点菜这一步骤,也是装饰者模式,鸳鸯锅就是被装饰者,麻辣牛肉、毛肚、鸭肠、菌肝、千层肚、鸭血、红糖糍粑就是装饰者;
装饰模式就是在不改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。
聊到这里,有没有一点饿?
现在呢?
走进装饰者模式
首先看一下装饰者模式的UML图
可以看出装饰模式中,有四个参与者:
- Component(抽象组件):定义对象的接口\抽象类,以规范准备接收附加责任的对象。
- ConcreteComponent(具体组件):具体的对象,抽象装饰者能给他新增职责
- Decorator(抽象装饰者):持有一个抽象组件对象的实例,并定义一个与抽象组件一致的接口。
- ConcreteDecorator(具体装饰者):具体的装饰对象。给内部持有的具体组件增加具体的职责;
我是按括号里的文字来记忆的,会比较容易记住。刚刚我们举例写到的“人”就是抽象组件;“男人\女人”就是具体组件;“装饰品”就是抽象组件;“帽子”就是具体组件;
装饰模式的特点
- 装饰者和被装饰者有相同的超类型
- 可以用一个或者多个装饰者包装一个对象
- 任何需要被装饰者对象的场合,可以用装饰过的对象代替它。(其实就是因为特点一)
- 装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为。(很重要)
- 对象可以在任何时候被装饰,包括运行时。
装饰模式的使用场合
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象增加/撤销职责。
- 当不能采用生成子类的方法进行扩充时。
活生生的例子
我们先来分析一下上面提到的人化妆的例子。首先来创建四个参与者。
1.抽象组件Human ,给他一个自我介绍的描述,再添加一个方法,说出自己穿了什么。
abstract class Human {
open var description = "I'm a human."
abstract fun getDress() : String
}
- 继承抽象组件的具体组件Male\Female,改变下自我描述。
class Male : Human() {
override var description: String
get() = "我是男性"
set(value) {}
override fun getDress(): String {
return "我穿了内裤"
}
}
- 继承抽象组件的抽象装饰者Decoration
abstract class Decoration : Human() {
abstract override var description: String
}
- 继承抽象装饰者的具体装饰者,这里我写一个帽子类,其他的随意添加。
class Hats : Decoration() {
override var description: String
get() = " 我有帽子"
set(value) {}
override fun getDress(): String {
return "帽子"
}
}
现在有了四个参与者,结构也按照UML写好了。那接下来就是包装了。
穿衣服时,我们会一件一件的穿(没有谁会同时穿吧?),所以,我们穿了裤子后,再穿衣服时,被装饰者是“男\女人+裤子”。看图!
装饰者是一个一个去包裹被装饰者,这里要注意,衣服和裤子跟男人是同一个超类,我们在包裹的时候,需要把被包装的对象(被包装的对象可能是具体组件,也可能是已经被装饰者装饰之后的具体组件),传到装饰者中,所以我们需要在具体装饰者类中添加一个超类参数。这样才能得到被包装对象的所有参数。刚刚的具体装饰者类还没写完,补充完整应该是:
class Hats(var human: Human) : Decoration() {
override var description: String
get() = "帽子"
set(value) {}
override fun getDress(): String {
return human.getDress() + " 帽子"
}
}
再创造几个具体装饰者类,此时项目结构就是这样的。
现在我们创造一个超人,给她穿上帽子,斗篷,并在界面中显示一下自己穿了些什么。
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()
}
}
结果如下:
总结一下
通过例子应该对装饰模式有个初步的理解了。再写demo的时候也只需要记住一下几步:
- 按照装饰模式UML图,写出四个参与者类。
- 在具体装饰者类的主构造函数中添加超类参数
- 父类引用指向子类对象创造具体组件
- 用具体装饰者装饰对象(需要什么装饰什么)
装饰模式优点:装饰模式比普通的继承更加灵活,能够在运行时更改组件的功能。而继承的类在运行前就决定了所有功能。
缺点:会创造很多的小类。别人看代码的时候会很脑壳痛。
注意:装饰模式中,装饰的顺序很重要。先穿“裤子”再穿“衣服”跟先穿“衣服”后穿“裤子”是不一样的
源码中的装饰模式Java I/O
我当初决定要学习设计模式的初衷,是为了看源码。现在我们就来一起来看看源码, 我也会顺便把我在这当中学到的一些看源码的方式方法说出来。大佬们请忽略这句话。
查看源码技巧一:查看它的父类子类并画图。
在AS右上角有个hierarchy按钮,点它可以查看当前类的直接父类和全部子类。没有这个按钮的话就按F4。
java I/O 是比较庞大的一个库,如果直接看其代码,很难知道每个类都在干啥,也不知道它究竟怎么运作的。实话说,我以前就看晕了。
通过看它的类结构。我们能画出这样一张图:
在这个设计中,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,年轻小伙竟作出这种事!