外观模式还是相当普遍的,先来看看几个例子:
1. 一台电脑有很多东西组成,包括硬件,软件。硬件又包含主板,CPU,内存什么的,软件又包含操作系统,操作系统里面又有一大堆非常复杂的驱动,各个驱动之间又有很多关系。那么我们怎么启动这么复杂的一个东西呢?我们所要做的就是按一下电源开关。当用户按下电源开关后,操作系统会把电脑运行起来,中间过程那是相当的复杂。对于用户来说,电源开关就是一个简单使用的接口,是一个facade。如果我们想进入安全模式,OK,在启动过程中按一下F8,那么这是另外一个facade,F8会调用一系列子系统来进入安全模式。
2. 一个手机有很多东西组成,那么怎么启动手机呢?也就是按一下电源。
3. 现在的汽车大多数都是手自一体的,那么自动档就是一个FACADE,通过自动档系统,用户根本就无需知道怎么时候挂1档,什么时候挂5档。当然对于那些有需求的驾驶员来说,他可以使用手动档(子系统)。
通过以上几个例子,我们基本可以知道外观模式(FACADE)其实就是给子系统包装一下,然后提供一个简单的接口让用户可以比较容易的使用。
GOF设计模式中是这么定义的:
意图
为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
结构图
上面的结构图,我们假设有一个子系统,子系统里面有很多子部件,子部件之间有很多耦合关系,那么如果让用户来调用这些子系统也不是不可以,但是可能会比较麻烦,或者让一个对子系统架构不熟悉的程序员来调用的话,还可能出现错误。如果我们可以提供一个facade类,这个facade会把子系统包装起来,然后里面提供一个Run函数,用户只需要调用Facade的Run就可以了,这就隐藏了子系统的调用过程,对于客户来说减少了大量的工作。
这里举一个非常简单的例子,我们还是继续沿用Composite和Decorator里面用过的例子。这次我们假设我们有一个子系统,这个子系统可以画直线,画正方形,画照片。子系统包括:CLine,CRect和CPhoto。其中CRect耦合了CLine,也就是说CRect是用CLine画出来的。子系统结构如下:
假如我们现在要画一个带框的照片(就是一个正方形,里面有个照片),该怎么画呢?可以这么做:
CLine* line = new CLine();
CRect* rect = new CRect(line);
CPhoto* photo = new CPhoto();
rect->Draw();
photo->Draw();
delete rect;
delete photo;
先生成一个CLine对象,然后传给CRect对象,再生成一个CPhone对象,然后按照顺序调用CRect的Draw()和CPhone的Draw()。
没有问题,我成功地画了一个正方形,然后正方形里面有个照片。但是这里有个问题:假如我们的程序员对这套子系统不熟悉怎么办呢?他得先学习一下这套子系统,包括子系统里面的子部件的关系,然后他才能正确地画出带框照片。如果我们在这套子系统的基础上提供一个Facade,会不会好点呢?给出代码:
#pragma once
#include <iostream>
class CGraphic
{
public:
virtual void Draw() = 0;
};
class CLine: public CGraphic
{
public:
virtual void Draw()
{
std::cout << "Draw line\n";
}
};
class CRect: public CGraphic
{
public:
CRect(CLine* line): _line(line)
{
}
virtual ~CRect()
{
if (_line)
{
delete _line;
_line = NULL;
}
}
virtual void Draw()
{
_line->Draw();
std::cout << "Draw rect\n";
}
protected:
CLine* _line;
};
class CPhoto: public CGraphic
{
public:
void Draw()
{
std::cout << "draw picture\n";
}
};
class CBorderPhotoFacade
{
public:
static CBorderPhotoFacade* GetInst()
{
static CBorderPhotoFacade facade;
return &facade;
}
virtual void DrawPhoto()
{
CLine* line = new CLine();
CRect* rect = new CRect(line);
CPhoto* photo = new CPhoto();
rect->Draw();
photo->Draw();
delete rect;
delete photo;
}
protected:
CBorderPhotoFacade()
{
}
};
上面的代码中,CGraphic,CLine,CRect,CPhoto共同组成了一个子系统。然后CBorderPhotoFacade是一个外观类,里面就提供了一个函数DrawPhoto(),这个函数会画出一个带框照片。那么客户端就可以这么调用:
CBorderPhotoFacade::GetInst()->DrawPhoto();
跟前面的版本相比较,从客户端的角度来讲,就是用CBorderPhotoFacade::DrawPhoto()代替了之前一系列子系统类的调用。也就是说,我们通过CBorderPhotoFacade类提供了一个高层接口,使得用户只需要调用这个高层接口,而无需去调用复杂的子系统。这个就是Facade模式的作用。
优点:
1. 它对客户屏蔽子系统组件,因而减少了客户处理的对象的数目使得子系统使用起来更加方便。上面的例子里面,客户端根本无需知道CLine,CRect,CPhoto,直接调用CBorderPhotoFacade就可以了。
2. 它实现了子系统和客户之间的松耦合关系;
3. 如果应用需要,客户还可以继续使用子系统。Facade只是提供一个高层接口,如果高层接口不能满足客户需要,那么直接调用子系统就是了。
进阶:
例子里面我们只是提供了一个Facade类,那么当我们需要提供多个Facade的时候,我们可以给Facade提供一个接口,然后子类化Facade。这样更灵活,当然付出的代价也大了,因为需要多增加几个类。或者我们可以给Facade类增加一个参数factory,然后把子系统对象的创建放到factory里面,也就是引入抽象工厂模式。这些都视情况而定,如果子系统比较复杂,那么就可以考虑Facade子类化或者直接引入抽象工厂类等等。
另外,通常来讲,Facade都是单例对象。我们的例子里面也把Facade类做成了单例类。