美文网首页
门面模式

门面模式

作者: 闹鬼的金矿 | 来源:发表于2022-06-16 19:13 被阅读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

    相关文章

      网友评论

          本文标题:门面模式

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