设计模式系列(十一)外观模式(Facade Pattern)
外观模式提供了统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。外观模式的意图是简化接口,让用户更加方便使用。举例来说,如果在家里布置了一个家庭影院,包括电影显示器,遥控器,爆米花机器等,当你想要享受一场电影的时候,你需要打开显示器、操作遥控器的一系列按钮、打开爆米花机器等,也就是说享受一场电影需要一个十分复杂的操作,然后等看电影结束后还需要逆向执行这一系列操作,这个过程十分复杂,大家估计都不想一步步自己亲手去做。而外观模式就是将家庭影院这个子系统的接口进行进一步封装,封装成一个高层接口,即所谓的外观,然后让用户直接调用高层接口,而高层接口负责调用低层接口,那么用户就可以直接选择看电影,紧接着上面的一系列操作就会自动执行,结束观影也是同样的道理,用户相当于只调用了高层接口的一个函数,其他的调用由高层函数执行,从而简化了接口。
虽然外观模式简化了接口,但是并不是说用户只能选择一键观影去调用高层接口,当用户想自己执行这一系列的操作时,也可以随时自己再去调用低层的这些操作,所以外观模式只是提供了一个简化的接口让用户调用,至于用户是否去调用是用户的选择,用户可以调用高层接口也可以选择去调用低层接口。
外观模式不仅简化了接口,而且也将客户从组件的子系统中解耦。外观和适配器可以包装许多类,但是外观的意图是简化接口,而适配器的意图是将接口转换成不同的接口。对于前面讲过的装饰者模式则是不改变接口,只是加入一定的责任或行为。
这里需要提到一个设计原则:“最少知识原则”,即只和你的密友谈话,意思就是说尽量不要让太多的类耦合在一起,例如不要对调用后返回的对象直接再进行调用,JAVA中的system.out.println()就是一个典型违反了最少知识原则的使用,不过这个设计原则要从整体来看,有时候使用,有时候不使用,要根据实际的系统权衡。
外观模式中的主要角色有:
(1)外观类(Facade):这个外观类就是用来封装子系统的接口,提供一个简单的接口给用户,一般外观类中都会包含很多子系统的对象,即使用了组合的方法,将子系统组合进外观中,然后将工作委托给子系统执行;
(2)客户(Clients):客户通过直接调用外观类中的简化后的接口来实现自己的需求;
(3)子系统(Subsystem):这个是要被外观类封装的低层接口的集合,一般有很多复杂的类,这些类之间也有复杂的关系,由外观类来进行封装简化。
在以下情况下可以考虑使用外观模式:
(1)设计初期阶段,应该有意识的将不同层分离,层与层之间建立外观模式;
(2) 开发阶段,子系统越来越复杂,增加外观模式提供一个简单的调用接口;
(3)维护一个大型遗留系统的时候,可能这个系统已经非常难以维护和扩展,但又包含非常重要的功能,为其开发一个外观类,以便新系统与其交互。
外观模式的优点是:
(1)松散耦合,即外观模式松散了客户端与子系统的耦合关系,让子系统内部的模块能更容易扩展和维护。
(2) 简单易用,即外观模式让子系统更加易用,客户端不再需要了解子系统内部的实现,也不需要跟众多子系统内部的模块进行交互,只需要跟外观交互就可以了,相当于外观类为外部客户端使用子系统提供了一站式服务;
(3)更好的划分访问层次,即通过合理使用Facade,可以帮助我们更好的划分访问的层次。有些方法是对系统外的,有些方法是系统内部使用的。把需要暴露给外部的功能集中到外观中,这样既方便客户端使用,也很好的隐藏了内部的细节。
外观模式的缺点是:就像上面说过的一样,使用外观模式后,用户也可以选择调用低层的接口,所以会让用户有所迷惑。
下面来看一个外观模式的例子,该例主要由三个文件组成,依次是:FacadePattern.h、FacadePattern.cpp、FacadePatternTest.cpp。
// 外观模式
// FacadePattern.h文件
#ifndef FACADE
#define FACADE
#include
#include
#include
#include
using std::string;
using std::cout;
using std::endl;
using std::vector;
// 外观模式中子系统的所有类
class Amplifier;
class Tuner;
class DvdPlayer;
class CdPlayer;
class Projector;
class TheaterLights;
class Screen;
class PopcornPopper;
class CdPlayer
{
public:
CdPlayer(string description, Amplifier* amplifier)
{
this->description = description;
this->amplifier = NULL;
}
void on();
void off();
void eject();
void play(string title);
void play(int track);
void stop();
void pause();
string toString();
private:
string description;
int currentTrack;
Amplifier* amplifier;
string title;
};
class Amplifier
{
public:
Amplifier(string description)
{
this->description = description;
this->tuner = NULL;
this->dvd = NULL;
this->cd = NULL;
}
void on();
void off();
void setStereoSound();
void setSurroundSound();
void setVolume(int level);
void setTuner(Tuner* tuner);
void setDvd(DvdPlayer* dvd);
void setCd(CdPlayer* cd);
string toString();
private:
string description;
Tuner* tuner;
DvdPlayer* dvd;
CdPlayer* cd;
};
class DvdPlayer
{
public:
DvdPlayer(string description, Amplifier* amplifier)
{
this->description = description;
this->amplifier = amplifier;
}
void on();
void off();
void eject();
void play(string movie);
void play(int track);
void stop();
void pause();
void setTwoChannelAudio();
void setSurroundAudio();
string toString();
private:
string description;
int currentTrack;
Amplifier* amplifier;
string movie;
};
class Projector
{
public:
Projector(string description, DvdPlayer* dvdPlayer)
{
this->description = description;
this->dvdPlayer = dvdPlayer;
}
void on();
void off();
void wideScreenMode();
void tvMode();
string toString();
private:
string description;
DvdPlayer *dvdPlayer;
};
class Screen
{
public:
Screen(string description)
{
this->description = description;
}
void up();
void down();
string toString();
private:
string description;
};
class Tuner
{
public:
Tuner(string description)
{
this->description = description;
}
void on();
void off();
void setFrequency(double frequency);
void setAm();
void setFm();
string toString();
private:
string description;
double frequency;
};
class TheaterLights
{
public:
TheaterLights(string description)
{
this->description = description;
}
void on();
void off();
void dim(int level);
string toString();
private:
string description;
};
class PopcornPopper
{
public:
PopcornPopper(string description)
{
this->description = description;
}
void on();
void off();
void pop();
string toString();
private:
string description;
};
// 外观模式中的外观类
class HomeTheaterFacade
{
public:
HomeTheaterFacade(Amplifier *amp,
Tuner *tuner,
DvdPlayer *dvd,
CdPlayer *cd,
Projector *projector,
Screen *screen,
TheaterLights *lights,
PopcornPopper *popper)
{
this->amp = amp;
this->tuner = tuner;
this->dvd = dvd;
this->cd = cd;
this->projector = projector;
this->screen = screen;
this->lights = lights;
this->popper = popper;
}
void watchMovie(string movie);
void endMovie();
void listenToCd(string cdTitle);
void endCd();
void listenToRadio(double frequency);
void endRadio();
private:
Amplifier *amp;
Tuner *tuner;
DvdPlayer *dvd;
CdPlayer *cd;
Projector *projector;
TheaterLights *lights;
Screen *screen;
PopcornPopper *popper;
};
#endif
// FacadePattern.cpp文件
#include "FacadePattern.h"
// 外观模式中子系统的所有类
void CdPlayer::on()
{
cout << description + " on" << endl;
}
void CdPlayer::off()
{
cout << description + " off" << endl;
}
void CdPlayer::eject() {
title = "";
cout << description + " eject" << endl;
}
void CdPlayer::play(string title)
{
this->title = title;
currentTrack = 0;
cout << description + " playing \"" + title + "\"" << endl;
}
void CdPlayer::play(int track)
{
char buf[4];
if (!title.compare(""))
{
sprintf_s(buf, "%d", currentTrack);
cout << description + " can't play track " + buf +
", no cd inserted" << endl;
}
else
{
currentTrack = track;
sprintf_s(buf, "%d", currentTrack);
cout << description + " playing track " + buf << endl;
}
}
void CdPlayer::stop()
{
currentTrack = 0;
cout << description + " stopped" << endl;
}
void CdPlayer::pause()
{
cout << description + " paused \"" + title + "\"" << endl;
}
string CdPlayer::toString()
{
return description;
}
void Amplifier::on()
{
cout << description + " on" << endl;
}
void Amplifier::off()
{
cout << description + " off" << endl;
}
void Amplifier::setStereoSound()
{
cout << description + " stereo mode on" << endl;
}
void Amplifier::setSurroundSound()
{
cout << description + " surround sound on (5 speakers, 1 subwoofer)" << endl;
}
void Amplifier::setVolume(int level)
{
char buf[4];
sprintf_s(buf, "%d", level);
cout << description + " setting volume to " + buf << endl;
}
void Amplifier::setTuner(Tuner* tuner)
{
cout << description + " setting tuner to " + dvd->toString() << endl;
this->tuner = tuner;
}
void Amplifier::setDvd(DvdPlayer* dvd)
{
cout << description + " setting DVD player to " + dvd->toString() << endl;
this->dvd = dvd;
}
void Amplifier::setCd(CdPlayer* cd)
{
cout << description + " setting CD player to " + cd->toString() << endl;
this->cd = cd;
}
string Amplifier::toString()
{
return description;
}
void DvdPlayer::on()
{
cout << description + " on" << endl;
}
void DvdPlayer::off()
{
cout << description + " off" << endl;
}
void DvdPlayer::eject()
{
movie = "";
cout << description + " eject" << endl;
}
void DvdPlayer::play(string movie)
{
this->movie = movie;
currentTrack = 0;
cout << description + " playing \"" + movie + "\"" << endl;
}
void DvdPlayer::play(int track)
{
char buf[4];
if (!movie.compare(""))
{
sprintf_s(buf, "%d", track);
cout << description + " can't play track " + buf + " no dvd inserted" << endl;
}
else
{
currentTrack = track;
sprintf_s(buf, "%d", track);
cout << description + " playing track " + buf + " of \"" + movie + "\"" << endl;
}
}
void DvdPlayer::stop()
{
currentTrack = 0;
cout << description + " stopped \"" + movie + "\"" << endl;
}
void DvdPlayer::pause()
{
cout << description + " paused \"" + movie + "\"" << endl;
}
void DvdPlayer::setTwoChannelAudio()
{
cout << description + " set two channel audio" << endl;
}
void DvdPlayer::setSurroundAudio()
{
cout << description + " set surround audio" << endl;
}
string DvdPlayer::toString()
{
return description;
}
void Projector::on()
{
cout << description + " on" << endl;
}
void Projector::off()
{
cout << description + " off" << endl;
}
void Projector::wideScreenMode()
{
cout << description + " in widescreen mode (16x9 aspect ratio)" << endl;
}
void Projector::tvMode()
{
cout << description + " in tv mode (4x3 aspect ratio)" << endl;
}
string Projector::toString()
{
return description;
}
void Screen::up()
{
cout << description + " going up" << endl;
}
void Screen::down()
{
cout << description + " going down" << endl;
}
string Screen::toString()
{
return description;
}
void Tuner::on()
{
cout << description + " on" << endl;
}
void Tuner::off()
{
cout << description + " off" << endl;
}
void Tuner::setFrequency(double frequency)
{
char buf[4];
sprintf_s(buf, "%d", frequency);
cout << description + " setting frequency to " + buf << endl;
this->frequency = frequency;
}
void Tuner::setAm()
{
cout << description + " setting AM mode" << endl;
}
void Tuner::setFm()
{
cout << description + " setting FM mode" << endl;
}
string Tuner::toString()
{
return description;
}
void TheaterLights::on()
{
cout << description + " on" << endl;
}
void TheaterLights::off()
{
cout << description + " off" << endl;
}
void TheaterLights::dim(int level)
{
char buf[4];
sprintf_s(buf, "%d", level);
cout << description + " dimming to " + buf + "%" << endl;
}
string TheaterLights::toString()
{
return description;
}
void PopcornPopper::on()
{
cout << description + " on" << endl;
}
void PopcornPopper::off()
{
cout << description + " off" << endl;
}
void PopcornPopper::pop()
{
cout << description + " popping popcorn!" << endl;
}
string PopcornPopper::toString()
{
return description;
}
void HomeTheaterFacade::watchMovie(string movie)
{
cout << "Get ready to watch a movie..." << endl;
popper->on();
popper->pop();
lights->dim(10);
screen->down();
projector->on();
projector->wideScreenMode();
amp->on();
amp->setDvd(dvd);
amp->setSurroundSound();
amp->setVolume(5);
dvd->on();
dvd->play(movie);
}
void HomeTheaterFacade::endMovie()
{
cout << "Shutting movie theater down..." << endl;
popper->off();
lights->on();
screen->up();
projector->off();
amp->off();
dvd->stop();
dvd->eject();
dvd->off();
}
void HomeTheaterFacade::listenToCd(string cdTitle)
{
cout << "Get ready for an audiopile experence..." << endl;
lights->on();
amp->on();
amp->setVolume(5);
amp->setCd(cd);
amp->setStereoSound();
cd->on();
cd->play(cdTitle);
}
void HomeTheaterFacade::endCd()
{
cout << "Shutting down CD..." << endl;
amp->off();
amp->setCd(cd);
cd->eject();
cd->off();
}
void HomeTheaterFacade::listenToRadio(double frequency)
{
cout << "Tuning in the airwaves..." << endl;
tuner->on();
tuner->setFrequency(frequency);
amp->on();
amp->setVolume(5);
amp->setTuner(tuner);
}
void HomeTheaterFacade::endRadio()
{
cout << "Shutting down the tuner..." << endl;
tuner->off();
amp->off();
}
// FacadePatternTest.cpp文件
#include "FacadePattern.h"
void main()
{
Amplifier amp("Top-O-Line Amplifier");
Tuner tuner("Top-O-Line AM/FM Tuner");
DvdPlayer dvd("Top-O-Line DVD Player", &);
CdPlayer cd("Top-O-Line CD Player", &);
Projector projector("Top-O-Line Projector", &dvd);
TheaterLights lights("Theater Ceiling Lights");
Screen screen("Theater Screen");
PopcornPopper popper("Popcorn Popper");
HomeTheaterFacade homeTheater(&, &tuner, &dvd, &cd,
&projector, &screen, &lights, &popper);
cout << "-----------------------------------------" << endl;
homeTheater.watchMovie("Raiders of the Lost Ark");
cout << "-----------------------------------------" << endl;
homeTheater.endMovie();
}
该例的运行结果如图1所示,UML类图如图2所示。
图1 运行结果
图2 UML类图
这个例子比较容易理解,HomeTheaterFacade类就是所谓的外观类,它组合了所有的子系统对象指针,然后对子系统的接口进行封装简化,提供简化后的接口给用户使用,图2中下面的一行类都是子系统中的类,可以看出这些类之间也有很多复杂的关系,只是外观类将用户调用简化了而已。