美文网首页iOS开发Swift开发@IT·互联网
用Swift整理GOF设计模式(四)--装饰模式

用Swift整理GOF设计模式(四)--装饰模式

作者: 星期五__ | 来源:发表于2016-02-04 20:08 被阅读424次

一、“单一职责”模式

概述:在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任.

这个思想的典型模式是:Decorator装饰模式、Bridge桥模式.
ps:这并非表示其他模式就不注重责任的问题,只是这两个模式在这个特性上尤为突出。

二、模式场景

这是一段关于流文件处理的代码思路

//业务操作
protocol Steam{
    func Read()
    func Seek()
    func Write()
}
//主体类
class FileStream:Steam{
    func Read() {}        //读文件流
    func Seek() {}        //定位文件流
    func Write() {}        //写文件流
}
class NetworkStream:Steam{
    func Read() { }         //读网络流
    func Seek() { }         //定位网络流
    func Write() {}         //写网络流
}
class MemoryStream:Steam{
    ...
    //内容类似
}

如上述代码所示,我们根据一个抽象协议,来创建了三种流,这样做很好,将流这个概念单独抽象出来。
那么下一个阶段我们接着思考如果,我们要再设计一个加密了的文件流,那似乎很简单,我们只需要使用继承,像这样写:

//扩展操作
class CryptoFileStream:FileStream{//加密的文件流,使用继承来操作
    override func Read() {
        //...省略额外的加密操作
        super.Read()
    }
    override func Seek() {
        //...省略额外的加密操作
        super.Seek()
    }
    override func Write() {
        //...省略额外的加密操作
        super.Write()
    }
}

这样的设计似乎很容易又很好,假如再有一些加密网络流和内存流的需求,So easy,照着路子做!

class CryptoNetworkStream:NetworkStream{//加密的内存流
    override func Read() {
        //...省略额外的加密操作
        super.Read()
    }
    override func Seek() {
        //...省略额外的加密操作
        super.Seek()
    }
    override func Write() {
        //...省略额外的加密操作
        super.Write()
    }
}
class CryptoMemoryStream:MemoryStream{//加密的内存流
    //略,如上类似
}

这个时候依然很好,似乎没有构成设计上的问题,那么再来一个需求,现在我们想在操作上需要一种拥有缓存操作的流了.假如你这个时候,看到这个需求,还没有意识到设计上的问题,那么你就可以阅读全文了.因为你肯定会如下设计:

class BufferedFileStream:FileStream{
    //...
}
class BufferedNetworkStream:NetworkStream{
    //...
}
class BufferedMemoryStream:MemoryStream{
    //...
}

还没完呢,这个时候自然而然,肯定会延伸出既加密又缓存的流的设计出现.那必然也会是这样:

class CryptoBufferedFileStream:FileStream{
    override func Read() {
        //额外的加密操作
        //额外的缓冲操作
        super.Read()
    }
    override func Seek() {
        //额外的加密操作
        //额外的缓冲操作
        super.Seek()
    }
    override func Write() {
        //额外的加密操作
        //额外的缓冲操作
        super.Write()
    }
}

class CryptoBufferedNetworkStream:NetworkStream{
       //略
}
class CryptoBufferedMemoryStream:MemoryStream{
       //略
}

这个时候你肯定闻到了一股坏坏的味道,因为这个设计似乎看上去漏洞百出,但是很多情况下,我们如果直接顺着需求,很容易演变成这样,而且这有时候意识到时,这个代码规模已经不小了.来看看这个设计的结构图:

设计.jpeg

假如在这个设计下,再加入类型流的设计,那得设计更多个类.这显然会造成代码的冗余.

三、尝试改良

我们可以从组合优于继承的角度,将上述的加密文件流,这样改写:

class CryptoFileStream{//加密操作
    var stream:FileStream?
    func Read() {
        //额外的加密操作
        stream?.Read()
    }
    func Seek() {
        //额外的加密操作
        stream?.Seek()
    }
    func Write() {
        //额外的加密操作
        stream?.Write()
    }
}

这样我们通过组合,在功能上,也和继承没有区别.然后我们再接着改写后面的内存流、网络流的加密后不难发现,这三段代码只有成员stream的类型不一样,其他都几乎一样了.于是乎,利用多态,我们将子类(FileStream)换成接口(Stream),将编译时绑定换成运行时绑定.这样的考虑,就取消了FileStream和NetworkStream这样类型的差异.

class CryptoStream{//加密操作,通过运行时绑定类型,替代编译时绑定,也就取消诸如CryptoFileStream的设计
    var stream:Stream?
    func Read() {
        //额外的加密操作
        stream?.Read()
    }
    func Seek() {
        //额外的加密操作
        stream?.Seek()
    }
    func Write() {
        //额外的加密操作
        stream?.Write()
    }
}

但还有一个问题,我们把Read,Seek,Write这三个方法的规范同时也取消了,于是我们接下来,进行如下微改

class CryptoStream:Stream{//继承的接口Stream,现在的目的变为规范接口
    var stream:Stream?
    init( st : Stream ){
        stream = st
    }
    func Read() {
        //额外的加密操作
        stream?.Read()
    }
    func Seek() {
        //额外的加密操作
        stream?.Seek()
    }
    func Write() {
        //额外的加密操作
        stream?.Write()
    }
}

这中间已经发生了非常大的变化,因为我们既规定了Stream的接口,又设置了stream作为多态成员.这是一个非常有意思的变化.并且我们只要这样来使用就能达到效果:

var s1 = FileStream()
var e1 = CryptoStream(st: s1)
var e2 = BufferedStream(st: e1)

而且这样做,通过运行时绑定,我只要像上面e1和e2的组合,就达到了原来结合版的效果.
到目前为止,我们的设计已经发生了极大的改变,现在结构图大致是这样:

设计2

三、完善装饰模式

这个时候我们其实代码还是要重复的部分,如 CryptoStream和BufferedStream还是会有重复的stream字段,如下面代码所示:

class CryptoStream:Stream{//加密操作
    var stream:Stream?
    init( st : Stream ){
        stream = st
    }
     ...//省略Read.Seek.Write方法
}
class BufferedStream:Stream{
    var stream:Stream?
    init( st : Stream ){
        stream = st
    }
    ...//省略Read.Seek.Write方法
}

那现在该怎么做,我们即使在抽象的Stream接口放入stream字段,还是要写出来,因为它不是类.而且就是把protocol换成class,FileSteam这样的类型类不需要这个stream多态字段.所以我们开始设计一个中间的基类

class DecoratorStream:Stream{
    var stream:Stream?
    init( st : Stream ){
        stream = st
    }
    func Read() {}
    func Seek() {}
    func Write() {}
}

这样我们的扩展类只需要继承这个中间类,就能达到目前的最佳效果了,如下所示:

class CryptoStream:DecoratorStream{//加密操作
    override func Read() {
        //额外的加密操作
        stream?.Read()
    }
    override func Seek() {
        //额外的加密操作
        stream?.Seek()
    }
    override func Write() {
        //额外的加密操作
        stream?.Write()
    }
}

现在改造算是完成了.我们从原来的1+n+n*m/2(n为类型类的数量,m为扩展类的数量)变成1+n+1+m。
这次的场景其实是源于对继承的不良使用,疯狂的使用继承.其实很多操作并不需要我们使用继承,静下心来分析,就能发现通过对象组合就能解决问题
全篇最妙的操作是指针和接口的如下使用:

class CryptoStream:Stream{//继承的接口Stream,现在的目的变为规范接口
    var stream:Stream?
    ...//省略操作
}

这是整个设计的核心,用指针来支持未来多态的变化.这就是装饰模式的妙处。Stream接口就像一层装饰一样,因此称之为装饰模式.

四、模式总结

下面是GOF对这个模式的评价:
动态地给一个对象增加一些额外的职责.就增加功能而言,Decorator模式比生成子类(继承)更为灵活(消除重复代码&减少子类个数)

要点总结如下:
1.通过组合而非继承的手法,Decorator模式实现了在运行时动态扩展对象功能的能力,而且根据需要扩展多个功能.而没有使用继承带来的"灵活性差"和"多子类衍生的问题"。
2.Decorator类在接口上表现为is - a的继承关系,即Decorator继承了Component类所具有的接口,但在实现上,表现了has a的关系.又使用了一个Component类。
3.Decorator模式的目的并非解决"多子类衍生的多继承"问题,Decorator模式应用要点在于解决"主体类多个方向上的扩展".
(主体操作和扩展操作应该像第二个结构,分开分支继承)

相关文章

网友评论

    本文标题:用Swift整理GOF设计模式(四)--装饰模式

    本文链接:https://www.haomeiwen.com/subject/sbvekttx.html