装饰模式算是一种很好理解的设计模式了,相信接触过设计模式的同学都会对这种模式印象深刻,但是可能你不知道,装饰模式在业界的应用也非常的广泛,甚至远超你的想象,让我们来一起看看吧。
1.模式介绍
装饰模式(Decorator Pattern)它的定义是这样的:Attach additional responsibilities to an object dynamically keeping the same interface. Decorators provide a flexible alternative to subclassing for extending functionality。
动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。
相较与继承,对象必须实现或者复用父类的函数或方法,装饰模式使用组合的方式代替继承,在不影响其他对象的情况下,动态地给一个对象添加一些额外的职责,更轻量的实现给原始类和对象增强功能的效果,在解决继承关系过于复杂、需要动态添加或者撤销职责的场景有很好的效果。
装饰模式其实从字面上更好理解:一个对象,我想要给他什么功能,就装饰什么功能,就像一间空屋子,用厨具的装饰方法来装饰,就会有厨房的功能,用书房的装饰方法来装饰,就会有书房的功能,以此类推,还有卧室、浴室等等。
主要解决继承关系过于复杂的场景。 组合优于继承,可以"使用组合来替代继承"
2.模式demo
2.1 UML
装饰模式(Decorator Pattern)的整体结构如下:
从UML图中,我们可以看到,装饰模式主要包含两个角色: 普通对象(Component)和装饰器(Decorator),装饰器和普通对象是聚合的关系,也就是说:普通对象和装饰器是部分与整体的关系,普通对象是装饰器的组成部分。
2.2 标准demo
我们依据标准的UML图,写出一个具体的例子(对应UML图):
(请放大图片观看)
首先定义Componet
(对照上图)接口: House
go
type House interface {
Live()
}
然后定义Componet
接口的实现类ConcreteComponet
(对照上图):DxmHouse
go
type DxmHouse struct{}
func (d *DxmHouse) Live() {
fmt.Println("dxmer are working")
}
然后定义包装类接口Decorator
(对照上图):
go
type Decorator struct {
h House
}
func (d *Decorator) SetHose(house House) {
d.h = house
}
之后分别定义包装类接口的两个实现类: KitchenDecorator
erlang
type KitchenDecorator struct {
Decorator
}
func (k *KitchenDecorator) Live() {
fmt.Println("---------厨房包装开始--------")
k.h.Live()
fmt.Println("---------厨房包装结束--------")
}
以及BedroomDecorator
erlang
type BedroomDecorator struct {
Decorator
}
func (b *BedroomDecorator) Live() {
fmt.Println("---------卧室包装开始---------")
b.h.Live()
fmt.Println("---------卧室包装结束---------")
}
运行调用函数:
css
func main() {
dxm := &DxmHouse{}
k := &KitchenDecorator{}
k.SetHose(dxm)
b := &BedroomDecorator{}
b.SetHose(k)
b.Live()
}
运行结果:
sql
---------卧室包装开始---------
---------厨房包装开始--------
dxmer are working
---------厨房包装结束--------
---------卧室包装结束---------
3. 源码解析
在Go的语言基础库中,经常能够看到很多场景使用了装饰模式。
3.1 GO语言IO库中的使用
Go中io包中的很多地方用了装饰模式,这里以bufio.Reader
为例子,首先定义一个被包装类的接口io.Reader
(请对照2.1UML图中的Component
)
go
// that happen after reading some bytes and also both of the
// allowed EOF behaviors.
//
// Implementations of Read are discouraged from returning a
// zero byte count with a nil error, except when len(p) == 0.
// Callers should treat a return of 0 and nil as indicating that
// nothing happened; in particular it does not indicate EOF.
//
// Implementations must not retain p.
type Reader interface {
Read(p []byte) (n int, err error)
}
然后定义io.Reader
的实现类os.File
(请对照2.1UML图中的ConcreteComponet
)
go
// File represents an open file descriptor.
type File struct {
*file // os specific
}
...
// Read reads up to len(b) bytes from the File and stores them in b.
// It returns the number of bytes read and any error encountered.
// At end of file, Read returns 0, io.EOF.
func (f *File) Read(b []byte) (n int, err error) {
if err := f.checkValid("read"); err != nil {
return 0, err
}
n, e := f.read(b)
return n, f.wrapErr("read", e)
}
之后定义io.Reader
的实现类bufio.Reader
(请对照2.1UML图中的Decorator
和ConcreteDecorator1
和ConcreteDecorator2
)
scss
// Buffered input.
// Reader implements buffering for an io.Reader object.
type Reader struct {
buf []byte
rd io.Reader // reader provided by the client
r, w int // buf read and write positions
err error
lastByte int // last byte read for UnreadByte; -1 means invalid
lastRuneSize int // size of last rune read for UnreadRune; -1 means invalid
}
// NewReader returns a new Reader whose buffer has the default size.
func NewReader(rd io.Reader) *Reader {
return NewReaderSize(rd, defaultBufSize)
}
...
// Read reads data into p.
// It returns the number of bytes read into p.
// The bytes are taken from at most one Read on the underlying Reader,
// hence n may be less than len(p).
// To read exactly len(p) bytes, use io.ReadFull(b, p).
// At EOF, the count will be zero and err will be io.EOF.
func (b *Reader) Read(p []byte) (n int, err error) {
n = len(p)
if n == 0 {
if b.Buffered() > 0 {
return 0, nil
}
return 0, b.readErr()
}
if b.r == b.w {
if b.err != nil {
return 0, b.readErr()
}
if len(p) >= len(b.buf) {
// Large read, empty buffer.
// Read directly into p to avoid copy.
n, b.err = b.rd.Read(p)
if n < 0 {
panic(errNegativeRead)
}
if n > 0 {
b.lastByte = int(p[n-1])
b.lastRuneSize = -1
}
return n, b.readErr()
}
// One read.
// Do not use b.fill, which will loop.
b.r = 0
b.w = 0
n, b.err = b.rd.Read(b.buf)
if n < 0 {
panic(errNegativeRead)
}
if n == 0 {
return 0, b.readErr()
}
b.w += n
}
// copy as much as we can
// Note: if the slice panics here, it is probably because
// the underlying reader returned a bad count. See issue 49795.
n = copy(p, b.buf[b.r:b.w])
b.r += n
b.lastByte = int(b.buf[b.r-1])
b.lastRuneSize = -1
return n, nil
}
函数有点长,可以无视,只关注结构体bufio.Reader
实现了Read
和NewReader
即可。
最后进行调用:
go
func main() {
f, err := os.Open("tmp")
if err != nil {
fmt.Println(err.Error())
return
}
reader := bufio.NewReader(f)
for {
msg, r := reader.ReadString('\n')
if r != io.EOF && r != nil {
fmt.Println(err.Error())
return
}
fmt.Println(msg)
if r == io.EOF {
break
}
}
}
可见bufio.Reader
实现了标准的装饰模式,以此类推,bufio.Writer
也是同样的。
其实不仅仅是Go语言,其他语言的IO标准库也大量的使用了装饰模式。
3.2 Go基础库Context
(请放大图观看)
首先回顾一下Context的用途:Context是一种用于跨多个Goroutine传递请求,协同工作的机制。正如它的名字一样,就是协程之间的上下文。 接下来看看它的实现机制,首先定义一个Context
接口:(请对照2.1UML图中的Component
)
scss
// A Context carries a deadline, a cancellation signal, and other values across
// API boundaries.
//
// Context's methods may be called by multiple goroutines simultaneously.
type Context interface {
Deadline() (deadline time.Time, ok bool)
//
Done() <-chan struct{}
//
Err() error
//
Value(key any) any
}
然后又定义了emptyCtx
结构体并实现了Context
(请对照2.1UML图中的ConcreteComponet
)
go
// An emptyCtx is never canceled, has no values, and has no deadline. It is not
// struct{}, since vars of this type must have distinct addresses.
type emptyCtx int
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
return
}
func (*emptyCtx) Done() <-chan struct{} {
return nil
}
func (*emptyCtx) Err() error {
return nil
}
func (*emptyCtx) Value(key any) any {
return nil
}
这是一个私有的结构体,实体主要用于background
和todo
两个变量,这也是context.Background()
和 context.TODO()
函数的返回值。如下代码:
scss
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
// Background returns a non-nil, empty Context. It is never canceled, has no
// values, and has no deadline. It is typically used by the main function,
// initialization, and tests, and as the top-level Context for incoming
// requests.
func Background() Context {
return background
}
// TODO returns a non-nil, empty Context. Code should use context.TODO when
// it's unclear which Context to use or it is not yet available (because the
// surrounding function has not yet been extended to accept a Context
// parameter).
func TODO() Context {
return todo
}
不同于标准的装饰类UML图,没有像Go语言的IO库一样那么标准,Context
没有实现2.1UML图中的Decorator
和实现类ConcreteDecorator1
、ConcreteDecorator2
,而是用各种函数替代:WithValue(...)
、WithTimeout(...)
、WithCancel(...)
、WithDeadline(...)
。
以上这几个函数会返回三个私有的结构体:cancelCtx
、valueCtx
、timerCtx
,三个结构体都实现了Context
接口,并且timerCtx
继承与cancelCtx
。
具体的结构请参照3.2开头的结构图。
valueCtx
arduino
// A valueCtx carries a key-value pair. It implements Value for that key and
// delegates all other calls to the embedded Context.
type valueCtx struct {
Context
key, val any
}
cancelCtx
go
// A cancelCtx can be canceled. When canceled, it also cancels any children
// that implement canceler.
type cancelCtx struct {
Context
mu sync.Mutex // protects following fields
done atomic.Value // of chan struct{}, created lazily, closed by first cancel call
children map[canceler]struct{} // set to nil by the first cancel call
err error // set to non-nil by the first cancel call
}
timerCtx
less
// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
// implement Done and Err. It implements cancel by stopping its timer then
// delegating to cancelCtx.cancel.
type timerCtx struct {
cancelCtx
timer *time.Timer // Under cancelCtx.mu.
deadline time.Time
}
4 总结
装饰模式的基本思想是通过组合和委托的方式,在不改变对象自身的情况下来动态增强对象的功能,通过装饰模式,可以将对象的功能分为不同的层级,每个层级的对象可以按照一定的顺序动态地组合进来,形成一个具有多种功能的对象。装饰模式的设计方式可以让项目能够更加灵活的组合对象,从而实现复杂的功能。
装饰模式的应用场景非常广泛,除了各类语言的语言IO基础库及Go的context之外,我们常用的Web框架中的router过滤器,也常常使用装饰模式去实现(还可能会用责任链实现,请参考博主Go语言设计模式之责任链模式 的文章)。