外觀模式
外觀模式(Facade Pattern)是一種結(jié)構(gòu)型設(shè)計(jì)模式,它提供了一個(gè)統(tǒng)一的接口颅停,用來(lái)訪問(wèn)子系統(tǒng)中的一群接口尺迂。外觀模式定義了一個(gè)高層接口,使得客戶(hù)端可以更加方便地使用子系統(tǒng)中的功能硅则,同時(shí)也降低了客戶(hù)端和子系統(tǒng)之間的耦合度淹父。
外觀模式主要解決的問(wèn)題是:當(dāng)一個(gè)系統(tǒng)中的多個(gè)組件之間存在復(fù)雜的依賴(lài)關(guān)系時(shí),客戶(hù)端需要了解和調(diào)用多個(gè)組件才能完成一個(gè)操作怎虫,這樣會(huì)導(dǎo)致客戶(hù)端代碼和子系統(tǒng)之間的耦合度較高暑认,維護(hù)和擴(kuò)展也較為困難。而外觀模式通過(guò)提供一個(gè)統(tǒng)一的接口大审,將多個(gè)組件封裝在一起蘸际,從而降低了客戶(hù)端代碼和子系統(tǒng)之間的耦合度,使得客戶(hù)端更加容易使用子系統(tǒng)中的功能徒扶。同時(shí)粮彤,外觀模式也使得子系統(tǒng)的接口更加易于維護(hù)和擴(kuò)展,因?yàn)橥庥^類(lèi)可以為客戶(hù)端提供功能的同時(shí),隱藏子系統(tǒng)中的復(fù)雜性导坟。
總的來(lái)說(shuō)屿良,外觀模式可以讓客戶(hù)端更加方便地訪問(wèn)子系統(tǒng)中的功能,同時(shí)也可以降低客戶(hù)端和子系統(tǒng)之間的耦合度惫周,提高系統(tǒng)的靈活性和可維護(hù)性尘惧。下面給出一個(gè)程序示例:
#include <iostream>
// 子系統(tǒng)類(lèi),實(shí)現(xiàn)具體的功能
class Subsystem1
{
public:
void operation1() { std::cout << "Subsystem1 operation1." << std::endl; }
};
class Subsystem2
{
public:
void operation2() { std::cout << "Subsystem2 operation2." << std::endl; }
};
class Subsystem3
{
public:
void operation3() { std::cout << "Subsystem3 operation3." << std::endl; }
};
// 外觀類(lèi)闯两,提供簡(jiǎn)單的接口給客戶(hù)端使用
class Facade
{
public:
void operation()
{
std::cout << "Facade operation." << std::endl;
m_subsystem1.operation1();
m_subsystem2.operation2();
m_subsystem3.operation3();
}
private:
Subsystem1 m_subsystem1;
Subsystem2 m_subsystem2;
Subsystem3 m_subsystem3;
};
int main()
{
Facade facade;
facade.operation();
return 0;
}
在上述代碼中褥伴,我們定義了一個(gè)外觀類(lèi) Facade
,它提供了簡(jiǎn)單的接口 operation
給客戶(hù)端使用漾狼。這個(gè)接口實(shí)際上是由三個(gè)子系統(tǒng)的具體操作組合而成的重慢。這三個(gè)子系統(tǒng)分別是 Subsystem1
、Subsystem2
和 Subsystem3
逊躁,它們實(shí)現(xiàn)了具體的功能似踱。在外觀類(lèi)中,我們將這三個(gè)子系統(tǒng)的實(shí)例作為私有成員變量稽煤,并在 operation
方法中依次調(diào)用它們的具體操作核芽。
客戶(hù)端使用外觀模式時(shí),只需要通過(guò)外觀類(lèi)的接口來(lái)調(diào)用功能酵熙,而不需要直接與子系統(tǒng)類(lèi)交互轧简。這樣可以減少客戶(hù)端代碼和子系統(tǒng)類(lèi)之間的耦合度,使系統(tǒng)更加靈活匾二、易于維護(hù)哮独。
當(dāng)運(yùn)行上述代碼時(shí),我們可以看到輸出結(jié)果如下:
Facade operation.
Subsystem1 operation1.
Subsystem2 operation2.
Subsystem3 operation3.
可以看到察藐,在客戶(hù)端調(diào)用外觀類(lèi)的接口 operation
時(shí)皮璧,系統(tǒng)依次調(diào)用了三個(gè)子系統(tǒng)的具體操作。這就是外觀模式的一種實(shí)現(xiàn)方式分飞。
Qt中的媒體播放框架
QMediaPlayer
其實(shí)就是外觀模式中的外觀(Facade)角色悴务,它為復(fù)雜的音視頻播放系統(tǒng)提供了一個(gè)簡(jiǎn)單的接口。在QMediaPlayer
的內(nèi)部譬猫,它使用了許多其他的類(lèi)和組件來(lái)實(shí)現(xiàn)音視頻播放的功能讯檐,這些類(lèi)和組件可以看作是外觀模式中的子系統(tǒng)。
具體來(lái)說(shuō)染服,以下是一些在QMediaPlayer
內(nèi)部可能會(huì)使用到的子系統(tǒng):
QMediaContent
:這個(gè)類(lèi)用來(lái)表示一個(gè)可以播放的媒體內(nèi)容裂垦,例如一個(gè)視頻文件或者一個(gè)網(wǎng)絡(luò)流。它可以被QMediaPlayer
用來(lái)加載音視頻內(nèi)容肌索。QMediaService
:這個(gè)類(lèi)提供了媒體播放的服務(wù)蕉拢,包括音頻解碼特碳、視頻解碼等。QMediaPlayer
可能會(huì)使用這個(gè)類(lèi)來(lái)獲取必要的媒體播放服務(wù)晕换。QMediaPlaylist
:這個(gè)類(lèi)表示一個(gè)媒體播放列表午乓。QMediaPlayer
可以使用這個(gè)類(lèi)來(lái)管理和播放多個(gè)音視頻文件。QAbstractVideoSurface
:如果QMediaPlayer
用于播放視頻闸准,那么可能會(huì)使用這個(gè)類(lèi)來(lái)顯示視頻畫(huà)面益愈。
這些都是在Qt媒體播放框架中可能會(huì)使用到的類(lèi)。需要注意的是夷家,QMediaPlayer
的實(shí)現(xiàn)可能會(huì)根據(jù)不同的平臺(tái)和版本有所不同蒸其,所以實(shí)際使用的類(lèi)和組件可能會(huì)有所不同。但是不論實(shí)現(xiàn)如何库快,QMediaPlayer
都會(huì)為用戶(hù)提供一個(gè)統(tǒng)一簡(jiǎn)單的接口摸袁,這就是外觀模式的主要目的。
下面給出QMediaPlayer
源碼如何體現(xiàn)外觀模式的一種程序示例义屏。請(qǐng)注意靠汁,這里的程序示例與實(shí)際代碼有一定的差異,隱藏了許多細(xì)節(jié)闽铐。但示意Qt源碼如何使用外觀模式蝶怔,應(yīng)該是勉強(qiáng)足夠的。另外兄墅,這里出現(xiàn)了QMediaPlayerPrivate
類(lèi)踢星,并使用了Q_DECLARE_PRIVATE
宏將其聲明為QMediaPlayer
的私有實(shí)現(xiàn)類(lèi)。實(shí)際上隙咸,這種設(shè)計(jì)方式稱(chēng)為Pimpl沐悦,即將類(lèi)的實(shí)現(xiàn)再封裝到一個(gè)叫私有實(shí)現(xiàn)類(lèi)的地方,一定程度上減少了編譯依賴(lài)扎瓶。這里不展開(kāi)講所踊,讀者若有興趣可以自行查閱相關(guān)資料泌枪,與本文的主題關(guān)系不大概荷。但要注意的是,Pimpl的實(shí)現(xiàn)方式在Qt源碼中是慣用手法碌燕,讀者最好能夠領(lǐng)會(huì)其含義以及習(xí)慣其代碼風(fēng)格误证,否則調(diào)試起來(lái)會(huì)有一些棘手。
class QMediaPlayerPrivate
{
public:
QMediaPlayerPrivate();
~QMediaPlayerPrivate();
void setMedia(const QMediaContent &media);
void setPlaylist(QMediaPlaylist *playlist);
void setVideoOutput(QAbstractVideoSurface *surface);
// Other methods...
QMediaContent currentMedia;
QMediaPlaylist *playlist;
QMediaService *service;
QAbstractVideoSurface *videoOutput;
};
class QMediaPlayer : public QMediaObject
{
Q_OBJECT
// ...
public:
explicit QMediaPlayer(QObject *parent = nullptr);
~QMediaPlayer();
void setMedia(const QMediaContent &media);
void setPlaylist(QMediaPlaylist *playlist);
void setVideoOutput(QAbstractVideoSurface *surface);
// ...
public Q_SLOTS:
void play();
void stop();
void pause();
// ...
private:
Q_DISABLE_COPY(QMediaPlayer)
Q_DECLARE_PRIVATE(QMediaPlayer)
};
在這個(gè)例子中修壕,QMediaPlayerPrivate
是QMediaPlayer
的私有實(shí)現(xiàn)類(lèi)愈捅,它包含了實(shí)際的媒體內(nèi)容、播放列表慈鸠、媒體服務(wù)和視頻輸出蓝谨。QMediaPlayerPrivate
有幾個(gè)私有成員變量來(lái)持有當(dāng)前的媒體內(nèi)容、播放列表、媒體服務(wù)和視頻輸出譬巫。這些成員變量代表了媒體播放系統(tǒng)的不同部分或子系統(tǒng)咖楣。
QMediaPlayer
類(lèi)在最后聲明了Q_DECLARE_PRIVATE(QMediaPlayer)
宏,實(shí)際相當(dāng)于聲明QMediaPlayer
有一個(gè)QMediaPlayerPrivate
的類(lèi)成員變量芦昔。QMediaPlayer
類(lèi)提供了一些公共方法來(lái)設(shè)置這些成員變量诱贿。例如,setMedia()
方法用于設(shè)置要播放的媒體內(nèi)容咕缎;setPlaylist()
方法用于設(shè)置播放列表珠十;play()
、stop()
和pause()
等方法來(lái)控制媒體的播放凭豪。這些方法將媒體播放的復(fù)雜性隱藏在QMediaPlayer
類(lèi)的內(nèi)部焙蹭,只提供一個(gè)簡(jiǎn)單的接口供用戶(hù)使用。需要注意墅诡,通過(guò)Pimpl的設(shè)計(jì)方式壳嚎,實(shí)際的程序邏輯是在QMediaPlayerPrivate
中進(jìn)行的。
總結(jié)
與組合模式一樣末早,外觀模式實(shí)際上更顯得“不值一提”烟馅。當(dāng)我們學(xué)習(xí)任何一門(mén)OO語(yǔ)言時(shí),都會(huì)使用外觀模式然磷。但實(shí)際上郑趁,在日常編程中很容易就會(huì)將一個(gè)類(lèi)在邏輯上的所有組成部分都塞到一個(gè)類(lèi)的實(shí)現(xiàn)里面,造成類(lèi)的實(shí)現(xiàn)過(guò)大姿搜,接口過(guò)于復(fù)雜寡润。
外觀模式是單一職責(zé)原則和最少知道原則的完美體現(xiàn)。我們應(yīng)有意識(shí)地去通過(guò)創(chuàng)建子系統(tǒng)類(lèi)舅柜,通過(guò)組合/聚合的方式組織在一起梭纹,并提供簡(jiǎn)單的接口對(duì)外界隱藏復(fù)雜性。而這個(gè)過(guò)程致份,就是實(shí)現(xiàn)外觀模式变抽。