门面模式

2022-06-16  本文已影响0人  闹鬼的金矿

对门面模式的理解,就是对一大堆复杂的功能做了简单的API封装。对于使用者来说,只需要记住少数几个简单的接口。最常见的例子就是平时会用到的各种三方库自身如何封装,如果运用好了门面模式,那么这个库对于使用者来说应该是非常友好。比如,可能用过两三次就能记住这个库核心极少数的几个类和方法。反之如果这个库对外接口包含了许多API,使用起来每次都需创建多个对象,调用不同的方法,可能这个库对外封装的API的设计不够友好。

使用UML描述门面模式是比较简单的,因为这里不涉及具体业务内部的各种细节和封装,只为了描述大致的思路。

下图是门面模式的UML结构图:

门面模式.png

Facade对象全局只有一个,它封装了对Client公开的接口,Client通过Facade来使用整个复杂的系统。第3部分是各种Subsystem即各个复杂的子系统了。

Additional Facade 可以理解为子门面对象(这里并不是继承的意思,它是通过组合来实现的),它封装了除开Facde使用到的一些 subsystem。Facde通过它来完成对某个子系统的调用,属于相对比较独立能够单独封装的门面对象,是Facde和subsystem之间新增的一层封装。Additional Facde可能有一个到多个。



现在通过一个视频文件格式转换的例子来说明,这个例子包含了一些音视频的知识,为了更好理解这个例子,以视频文件播放的过程为例子先了解一下相关知识:

视频播放工程.png

一个视频文件分为两个部分:视频部分和音频部分,所以它的播放是需要分成两部分处理的。

首先要做的是将视频和音频文件分开,也就是解封装格式数据。比如视频部分得到了H.264文件,音频部分变成ACC文件。接下来对这两部分分别进行解码处理,视频H.264获得了原始YUV文件,音频的AAC文件得到了原始PCM文件。解码之后的文件可以直接通过显示器和扬声器进行播放,在播放之前,需要同步音视频文件。保证在某一个具体的时间,视频的内容和音频的内容是匹配的。

在这个例子中,为了简单说明问题,简化了音视频两部分的内容,融合到了一起,只有最后一步混合同步与上面例子一致。现在来看一下具体代码:

VideoFile: 通过文件路径,获取文件编解码信息:

class VideoFile {
    let fileName: String
    let sc: SourceCodec
    
    init(fn: String) {
        self.fileName = fn
        sc = AVICodec()
    }
}

Result: 最终转换格式之后生成的对象,包含了具体的二进制流数据和转换之后的名称信息:

class Result {
    let buffer: Buffer
    let name: String
    init(b: Buffer, n: String) {
        buffer = b
        name = n
    }
}

File: 对Result的封装,主要用于将Result对象保存到文件中

class File {
    let result: Result
    
    init(r: Result) {
        self.result = r
    }
    
    func save() {
        let n = result.name
        print("保存\(n)")
    }
}

SourceCodec是各种格式编码对象的基类,包含格式信息

class SourceCodec {
    func name() -> String {
        return ""
    }
}

接着是AVI, OGG, MPEG4 三种视频格式类

class AVICodec: SourceCodec {
    override func name() -> String {
        return "AVI"
    }
}

class OggCompressionCodec: SourceCodec {
    override func name() -> String {
        return "OGG"
    }
}

class MPEG4CompressionCodec: SourceCodec {
    override func name() -> String {
        return "MPEG4"
    }
}

Buffer包含文件信息,是解码之后的二进制流,在文件转换之前需要先解码成Buffer,再将Buffer编码为目标格式的文件

class Buffer {
    
    let filePath: String
    
    init(fn: String) {
        filePath = fn
    }
}

AudioMixer用于将音频和视频进行混合同步:

class AudioMixer {
    func fix(r: Result) -> Result {
        print("将音频和视频数据进行同步")
        return r
    }
}

BitrateReader就是具体编解码和转换的工具类

class BitrateReader {
    class func read(fn: String, sc: SourceCodec) -> Buffer{
        print("读取\(fn) 通过 \(sc.name()) 解码获得二进制流")
        return Buffer(fn: fn)
    }
    class func convert(bf: Buffer, dc: SourceCodec) -> Result {
        let n = dc.name()
        print("将二进制转换为\(n) 格式的二进制")
        return Result(b: bf, n: dc.name())
    }
}

CodecFactory通过文件名获取编码信息,可以把它理解为Additional Fadade

class CodecFactory {
    class func extract(vf: VideoFile) -> SourceCodec {
        return vf.sc
    }
}

Facde对象,提供了对外的接口:传入原文件和目标格式,返回转换之后的File对象。它将文件读取,编解码以及转换的过程,混合等过程都封装到了一起。

class VideoConverter {
    func convert(fn: String, format: String) -> File {
        
        let file = VideoFile(fn: fn)
        let sourceCodec = CodecFactory.extract(vf: file)
        var destinationCodec:SourceCodec = OggCompressionCodec()
        if format == "mp4" {
            destinationCodec = MPEG4CompressionCodec()
        }
        let buffer = BitrateReader.read(fn: fn, sc: sourceCodec)
        var result = BitrateReader.convert(bf: buffer, dc: destinationCodec)
        result = AudioMixer().fix(r: result)
        return File(r: result)
    }
}

最后的测试方法:

func testFacade() {
    let convertor = VideoConverter()
    let mp4 = convertor.convert(fn: "abc.avi", format: "mp4")
    mp4.save()
}

Reference: Dive Into DESIGN PATTERNS

上一篇下一篇

猜你喜欢

热点阅读