门面模式
对门面模式的理解,就是对一大堆复杂的功能做了简单的API封装。对于使用者来说,只需要记住少数几个简单的接口。最常见的例子就是平时会用到的各种三方库自身如何封装,如果运用好了门面模式,那么这个库对于使用者来说应该是非常友好。比如,可能用过两三次就能记住这个库核心极少数的几个类和方法。反之如果这个库对外接口包含了许多API,使用起来每次都需创建多个对象,调用不同的方法,可能这个库对外封装的API的设计不够友好。
使用UML描述门面模式是比较简单的,因为这里不涉及具体业务内部的各种细节和封装,只为了描述大致的思路。
下图是门面模式的UML结构图:
门面模式.pngFacade对象全局只有一个,它封装了对Client公开的接口,Client通过Facade来使用整个复杂的系统。第3部分是各种Subsystem即各个复杂的子系统了。
Additional Facade 可以理解为子门面对象(这里并不是继承的意思,它是通过组合来实现的),它封装了除开Facde使用到的一些 subsystem。Facde通过它来完成对某个子系统的调用,属于相对比较独立能够单独封装的门面对象,是Facde和subsystem之间新增的一层封装。Additional Facde可能有一个到多个。
现在通过一个视频文件格式转换的例子来说明,这个例子包含了一些音视频的知识,为了更好理解这个例子,以视频文件播放的过程为例子先了解一下相关知识:
一个视频文件分为两个部分:视频部分和音频部分,所以它的播放是需要分成两部分处理的。
首先要做的是将视频和音频文件分开,也就是解封装格式数据。比如视频部分得到了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()
}