什么是装饰者模式
好久没有更新设计模式系列的博客了, 今天我们来聊一聊装饰者模式, 用过java的同学肯定对装饰者模式非常熟悉,就算你不知道什么是装饰者模式这概念, 你也一定在代码中经常用到这个模式,为什么这么说呢? 大家都用过java中的流吧, 我们可以这样写:
new BufferedOutputStream(new FileOutputStream());
大家对这样的代码肯定很熟悉了, 用另外一个类包装一下另外一个类, 或方便了我们的使用, 或增强了功能. 不是说设计模式嘛, 怎么扯开流了… 其实java中这种io操作的代码正式装饰者模式的一种使用.
那它有什么特点呢?
- 理论上它们是可以无限包装的.
- 装饰者和被装饰者们有相同的超类型(super).
- 想要拓展功能无需修改原有的代码, 定义一个装饰者就可以.
看了这些特点和上面的小段代码,不禁让我们想到了前面说的适配器模式, 越看越想适配器模式! 那他们有什么区别吗?其实区别很简单:
适配器模式是在类型不匹配的时候使用, 目的是将一种类型伪装成另一种类型以便我们的代码可以正常使用;而装饰者模式装饰者和被装饰者拥有相同的类型(相同的超类),目的是为了增强功能或者方便使用.
看完了区别,我们再从上面的代码和特点中找一下装饰者模式都是用了哪些设计原则.
- 从”包装”我们可以看到”多用组合,少用继承”
- 从”拓展”我们可以看到”开闭原则”
在不必改变原类文件和使用继承的情况下, 动态地扩展一个对象的功能. 它是通过创建一个包装对象, 也就是装饰来包裹真实的对象.
概念多看几遍,对照下面的代码理解一下就ok了.扯完了概念,下面我们就应该开始实战一下, 实现一下这个设计模式了.
代码描述环节
在上班的时候, 我和同时经常去路边吃各种路边摊, 吃的最多的还是各种面, 有纯面条的, 面条加鸡蛋的, 面条加火腿肠的… 下面我们就以面条为例来实现一下代码.
首先我们先来定义一个超类型, 也就是一个接口,用来规范面条的几个方法.
type Noddles interface {
Description() string
Price() float32
}
只有两个方法, 一个是面条的描述,另一个是价格. 接着我们搞一个拉面出来,
type Ramen struct {
name string
price float32
}
func (p Ramen) Description() string {
return p.name
}
func (p Ramen) Price() float32 {
return p.price
}
拉面有两个属性name
和price
, 同样他有两个方法Description
和Price
, 所以它实现了上面的Noddles
接口. 不着急下面的代码,我们先来造一碗面条尝尝.
面条出来了, 不过光吃面条不行,我想吃加蛋的面条, 咋办? 重写Ramen
让他支持加蛋吗? 当然不行, 那以后我们还要加香肠,加西红柿呢? 难道每次推出新品种都要修改Ramen
吗?
这当然不是一个好办法, 这个时候我们就可以使用装饰者模式了. 不就是加个鸡蛋嘛, 我们再定义一个鸡蛋的装饰者!
type Egg struct {
noddles Noddles
name string
price float32
}
func (p Egg) SetNoddles(noddles Noddles) {
p.noddles = noddles
}
func (p Egg) Description() string {
return p.noddles.Description() + "+" + p.name
}
func (p Egg) Price() float32 {
return p.noddles.Price() + p.price
}
这个鸡蛋装饰者比上面的Ramen
多了一个Noddles
类型的属性, 这个属性也就是我们将要装饰的类型, 我们下面提供了SetNoddles
来设置它. 好了,现在鸡蛋装饰者有了, 可以给我搞一个
加鸡蛋的拉面了吧.
ramen := Ramen{name: "ramen", price: 10}
egg := Egg{noddles: ramen, name: "egg", price: 2}
fmt.Println(egg.Description())
fmt.Println(egg.Price())
我们再初始化Egg
的时候只需要指定一下要被装饰的对象就可以了. 来看看加了鸡蛋的拉面长啥样.
鸡蛋是加上了, 价格也变贵了. 突然有一天我想吃带香肠的鸡蛋拉面了, 你说咋办? 很简单, 依鸡蛋画瓢, 搞出一个香肠的装饰者来.
type Sausage struct {
noddles Noddles
name string
price float32
}
func (p Sausage) SetNoddles(noddles Noddles) {
p.noddles = noddles
}
func (p Sausage) Description() string {
return p.noddles.Description() + "+" + p.name
}
func (p Sausage) Price() float32 {
return p.noddles.Price() + p.price
}
看看我这个加了香肠和鸡蛋的拉面吧.
现在香肠也加上了, 不过价格又高了, 虽然价格贵了点,不过我们终于搞明白什么是装饰者模式了, 还是很开心的. 到现在为止, 我们不仅可以吃上香肠鸡蛋面, 还可以吃双蛋面!
ramen := Ramen{name: "ramen", price: 10}
egg := Egg{noddles: ramen, name: "egg", price: 2}
egg2 := Egg{noddles: egg, name: "egg", price: 2}
fmt.Println(egg2.Description())
fmt.Println(egg2.Price())
突然发现, 现在我可以吃任意组合的面了, 好开心, 你可以试试加4个鸡蛋5根香肠啥效果.
总结: 不总结了, 上面扯概念的时候扯的差不多了, 在装饰者模式中我们再一次见识到了组合的魅力, 所以多用组合,少用继承.
代码放github上了,欢迎star: https://github.com/qibin0506/go-designpattern