C++设计模式——外观模式

前言

    自己有过组装电脑的人都知道,需要到电子市场去购买CPU、主板、硬盘、内存、显示器、光驱等配件。这个方案是好,但需要对各种配件都比较熟悉,这样才能选择最合适的配件,而且还需要考虑配件之间的兼容性。还有一个方案,就是到电子城,找一家专门组装电脑的公司,把自己的要求提出来,然后等着提电脑就好了。我们不需要关心要购买什么样的配件,也不需要知道电脑是如何组装起来的,所有这些操作都交给电脑组装公司,由它代为完成。在设计模式中,也存在一种类似的模式,完成某个功能需要调用其它多个模块,由外观类去调用这些模块来完成功能,这样客户端就不用跟系统中的多个模块交互,也不用知道那么多模块的细节了,称之为外观模式。

1、外观模式概述

    在软件开发中,有时候为了完成一项较为复杂的功能,一个客户类需要和多个业务类交互,而这些需要交互的业务类经常会作为一个整体出现,由于涉及到的类比较多,导致使用时代码较为复杂,此时,特别需要一个类似电脑组装公司一样的角色,由它来负责和多个业务类进行交互,而客户类只需与该类交互。外观模式通过引入一个新的外观类(Facade)来实现该功能,外观类充当了软件系统中的“电脑组装公司”,它为多个业务类的调用提供了一个统一的入口,简化了类与类之间的交互。在外观模式中,那些需要交互的业务类被称为子系统(Subsystem)。如果没有外观类,那么每个客户类需要和多个子系统之间进行复杂的交互,系统的耦合度将很大,而引入外观类之后,客户类只需要直接与外观类交互,客户类与子系统之间原有的复杂引用关系由外观类来实现,从而降低了系统的耦合度。

外观模式示意图

外观模式:为子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

    外观类将客户类与子系统的内部复杂性分隔开,使得客户类只需要与外观角色打交道,而不需要与子系统内部的很多对象打交道。它是一种对象结构型模式。外观模式是迪米特法则的一种具体实现,通过引入一个新的外观角色可以降低原有系统的复杂度,同时降低客户类与子系统的耦合度。

外观模式结构图

    Facade(外观角色):在客户端可以调用它的方法,在外观角色中可以知道相关的(一个或者多个)子系统的功能和责任;在正常情况下,它将所有从客户端发来的请求委派到相应的子系统去,传递给相应的子系统对象处理。外观角色只负责委派操作,而不参与子系统的业务逻辑,为什么呢?因此外观对象只是提供一个访问子系统的一个路径而已,它不应该也不能参与具体的业务逻辑,违背了"单一原则",导致外观对象既负责业务处理,也负责委托操作。

    SubSystem(子系统角色):在软件系统中可以有一个或者多个子系统角色,每一个子系统可以不是一个单独的类,而是一个类的集合,它实现子系统的功能;每一个子系统都可以被客户端直接调用,或者被外观角色调用,它处理由外观类传过来的请求;子系统并不知道外观的存在,对于子系统而言,外观角色仅仅是另外一个客户端而已。

    外观模式的主要目的在于降低系统的复杂程度,在面向对象软件系统中,类与类之间的关系越多,不能表示系统设计得越好,反而表示系统中类之间的耦合度太大,这样的系统在维护和修改时都缺乏灵活性,因为一个类的改动会导致多个类发生变化,而外观模式的引入在很大程度上降低了类与类之间的耦合关系。引入外观模式之后,增加新的子系统或者移除子系统都非常方便,客户类无须进行修改(或者极少的修改),只需要在外观类中增加或移除对子系统的引用即可。从这一点来说,外观模式在一定程度上并不符合开闭原则,增加新的子系统需要对原有系统进行一定的修改,虽然这个修改工作量不大。

    外观模式中所指的子系统是一个广义的概念,它可以是一个类、一个功能模块、系统的一个组成部分或者一个完整的系统。子系统类通常是一些业务类,实现了一些具体的、独立的业务功能,其典型代码如下:

//子系统A
class SubSystemA
{
public:
	void MethodA()
	{
		//业务实现代码
	}
};


//子系统B
class SubSystemB
{
public:
	void MethodB()
	{
		//业务实现代码
	}
};


//子系统C
class SubSystemC
{
public:
	void MethodC()
	{
		//业务实现代码
	}
};

在引入外观类之后,与子系统业务类之间的交互统一由外观类来完成,在外观类中通常存在如下代码:

//外观类
class Facade
{
private:
	//维持子系统的引用
	SubSystemA * m_pSubSystemA;
	SubSystemB * m_pSubSystemB;
	SubSystemC * m_pSubSystemC;
public:
	//创建各子系统对象
	Facade()
	{
		m_pSubSystemA = new SubSystemA();
		m_pSubSystemB = new SubSystemB();
		m_pSubSystemC = new SubSystemC();
	}

	//销毁子系统对象
	~Facade()
	{
		delete m_pSubSystemA;
		m_pSubSystemA = NULL;

		delete m_pSubSystemB;
		m_pSubSystemB = NULL;

		delete m_pSubSystemC;
		m_pSubSystemC = NULL;
	}

	//委派操作
	void Method()
	{
		m_pSubSystemA->MethodA();
		m_pSubSystemB->MethodB();
		m_pSubSystemC->MethodC();
	}
};
由于在外观类中维持了对子系统对象的引用,客户端可以通过外观类来间接调用子系统对象的业务方法,而无须与子系统对象直接交互。引入外观类后,客户端代码变得非常简单,典型代码如下:

int main()
{
	Facade * pFacade = new Facade()

	pFacade->Method();

	delete pFacade;
	pFacade = NULL;
}

2、手机备份软件的设计与实现

某软件公司将开发一款手机备份软件,功能如下:能够对手机中的通讯录,短信,照片这些资源进行备份。通讯录,短信,照片备份是三个独立的模块。请用外观模式对此进行设计。

2.1 不使用外观模式的实现方式

    TelphoneNumber为通讯录备份模块、ShortMessage为短信备份模块、Image为照片备份模块。实现代码如下:

#ifndef _RESOURSE_H_
#define _RESOURSE_H_

#include 
#include 

using namespace std;


//电话号码类
class TelphoneNumber
{
public:
	void BakeUpTelphoneNumber()
	{
		cout << "备份电话号码" << endl;
	}
};


//短信类
class ShortMessage
{
public:
	void BakeUpShortMessage()
	{
		cout << "备份短信" << endl;
	}
};



//照片类
class Image
{
public:
	void BakeUpImage()
	{
		cout << "备份照片" << endl;
	}
};

#endif
测试代码实现如下:
#include 
#include "Resourse.h"
using namespace std;

int main()
{
	/*******************备份电话号码**************************/
	TelphoneNumber * pTelphoneNumber  = new TelphoneNumber();
	pTelphoneNumber->BakeUpTelphoneNumber();

	/*******************备份短信**************************/
	ShortMessage * pShortMessage = new ShortMessage();
	pShortMessage->BakeUpShortMessage();

	/*******************备份照片**************************/
	Image * pImage = new Image();
	pImage->BakeUpImage();

	/*******************销毁操作**************************/
	delete pTelphoneNumber;
	pTelphoneNumber = NULL;
	delete pShortMessage;
	pShortMessage = NULL;
	delete pImage;
	pImage = NULL;

	return 0;
}
编译并运行结果如下:


    虽然上述代码能实现对电话号码,短信,照片进行备份。但客户端直接操作电话备份、短信备份、照片备份模块,客户端需要知道备份的具体细节,客户端与这三个类耦合性升高了,违背迪迷特法则,最小朋友原则。现在只在客户端进行备份操作,如果其他地方也需要备份操作,复用性不强;如果备份操作进行了修改,则多个地方将进行修改,维护性差。因此需要对上述代码进行重构,封装一个外观类,由外观类对三种资源进行备份操作,使得客户类与备份模块解耦。

2.2 使用外观模式的实现方式

    备份模块代码没有变化,实现代码如下:

#ifndef _RESOURSE_H_
#define _RESOURSE_H_

#include 
#include 

using namespace std;


//电话号码类
class TelphoneNumber
{
public:
	void BakeUpTelphoneNumber()
	{
		cout << "备份电话号码" << endl;
	}
};


//短信类
class ShortMessage
{
public:
	void BakeUpShortMessage()
	{
		cout << "备份短信" << endl;
	}
};



//照片类
class Image
{
public:
	void BakeUpImage()
	{
		cout << "备份照片" << endl;
	}
};


#endif

添加一个备份外观类BakeupFacade,由它对备份模块进行操作,解除客户类与具体备份模块的耦合。 它将所有从客户端发来的请求委派到相应的子系统去,传递给相应的子系统对象处理。

#ifndef _FACADE_H_
#define _FACADE_H_

#include "Resourse.h"


//备份外观
class BakeupFacade
{
private:
	//维护对电话号码、短信、照片对象的引用
	TelphoneNumber * m_pTelphoneNumber;
	ShortMessage * m_pShortMessage;
	Image * m_pImage;
public:
	//外观构造函数,创建手机,短信,照片对象
	BakeupFacade()
	{
		m_pTelphoneNumber = new TelphoneNumber();
		m_pShortMessage = new ShortMessage();
		m_pImage = new Image();
	}

	//备份资源
	void BakeupResourse()
	{
		m_pTelphoneNumber->BakeUpTelphoneNumber();
		m_pShortMessage->BakeUpShortMessage();
		m_pImage->BakeUpImage();
	}
};


#endif

 测试代码实现如下:

#include 
#include "Facade.h"
using namespace std;


int main()
{
	//创建外观对象
	BakeupFacade * pBakeupFacade = new BakeupFacade();

	//备份
	pBakeupFacade->BakeupResourse();

	//销毁对象
	delete pBakeupFacade;
	pBakeupFacade = NULL;

	return 0;
}

编译并执行,结果如下:


    引入了外观类,外观类对备份模块进行操作,解除了客户类与备份模块的耦合性。客户类不需要直接操作子系统,而是由外观类负责处理,对客户端而言是透明的,客户类只需要操作外观类就可以了,符合"迪迷特法则"。如果多个地方需要Facade,也就是说外观可以实现功能的共享,也就是实现复用。同样的调用代码只用在Facade里面写一次就好了,不用在多个调用的地方重复写。如果某个备份模块需要修改,只需要修改这个备份模块就可以了,对客户端无影响,维护性好。

3、外观模式总结

    引入了外观类,解除了客户类与子系统的耦合性。客户类不需要直接操作子系统,而是由外观类负责处理,对客户端而言是透明的,客户类只需要操作外观类就可以了,符合"迪迷特法则"。如果多个地方需要Facade,也就是说外观可以实现功能的共享,也就是实现复用,同样的调用代码只用在Facade里面写一次就好了,不用在多个调用的地方重复写。如果某个系统模块需要修改,只需要修改这个系统模块就可以了,对客户端无影响,维护性好。还有一个潜在好处,对使用Facade的人员来说,Facade节省了他们的学习成本,他们只需要了解Facade即可,无需深入到子系统内部,去了解每个模块的细节,也不用和多个模块交互,从而使得开发简单,学习也容易。

    现在的系统是越做越大,越来越复杂,对软件的要求也越来越高。为了提高系统的可重用性,通常会把一个大的系统分为多个子系统,再把一个子系统分成很多更小的子系统,一直分下去,分到一个个小的模块,这样一来子系统的重用性就会得到加强。但这也带来一个问题,由于模块增加,对象之间的耦合性也随之增强了。外观模式可以解决这个问题,由外观来对各个子模块子系统进行处理,客户端只需要操作外观就可以了。

    通常在三层架构中就需要考虑在数据访问层和业务逻辑层、业务逻辑层和UI层的层与层之间建立外观Facade,这样可以为复杂的子系统提供一个简单的结构,使得耦合大大降低。例如:在UI层定义一个外观对象,外观负责对应用层的子系统进行处理,当UI层有请求时,通过UI层的外观委托给应用层的子系统进行处理。同样可以在应用层定义一个外观对象,当应用层请求对数据进行处理的时候,通过应用层的外观委托给数据层中的模块对数据进行处理。

3.1 外观模式优点

    外观模式的主要优点如下:

    (1) 它对客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并使得子系统使用起来更加容易。通过引入外观模式,客户端代码将变得很简单,与之关联的对象也很少,符合“迪米特法则”。

    (2) 它实现了子系统与客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的客户端,只需要调整外观类即可。

    (3) 一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。

    (4)外观对象负责对子系统进行处理,如果某个子系统进行了修改,可能只需要修改外观对象就可以了,维护性好。如果没有使用外观对象,则某个子系统进行了修改,则如果有多个客户端使用到这个子系统,则这些客户端都得进行修改,维护性差。

    (5)多个客户端可以复用外观Facade,复用性好。

    (6)符合多用组合少用继承原则。

3.2 外观模式缺点

    外观模式的主要缺点如下:

    (1) 不能很好地限制客户端直接使用子系统类,如果对客户端访问子系统类做太多的限制则减少了可变性和灵活 性。

    (2) 如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则。

3.3 外观模式适用场景

    (1) 当要为访问一系列复杂的子系统提供一个简单入口时可以使用外观模式。

    (2) 客户端程序与多个子系统之间存在很大的依赖性。引入外观类可以将子系统与客户端解耦,从而提高子系统的独立性和可移植性。

    (3) 在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度

3.4 外观模式具体应用

    (1)邮件业务系统的开发: 发信人只需要把发送内容,接收地址告诉邮局,邮局系统代为发送处理。邮局系统充当外观角色。

    (2)绝大多数系统都有一个首页或者导航页面,同时也提供了菜单或者工具栏,在这里,首页和导航页面、菜单和工具栏就是外观角色,通过它们用户可以快速访问子系统,降低了系统的复杂程度。

    (3)KTV点歌系统的开发: 提供一个外观用来处理显示器模块、音响模块、灯光模块操作。

    (4)文件安全传送模块开发: 存在三个模块,读取文件内容模块,对文件内容进行加密模块和写入文件模块。提供一个外观,对这些模块进行处理。

    (5)在三层架构中,UI层和业务逻辑层提供一个外观,业务逻辑层和数据访问层提供一个外观,这样可以为复杂的子系统提供一个简单的结构。

    (6)车票订购系统的开发: 顾客只需要到售票窗口,购买所需要的火车票,汽车票、飞机票。由售票窗口统一处理。

    (7)医疗系统的开发: 目前需要挂号、缴费、取药等流程。可以定义一个外观,让外观处理这些流程。

    (8)Windows开机启动程序: 应用程序可以双击开启,也可以由Winodws代为处理,windows开机自动运行应用程序,这个时候,Windows系统就是一个外观。

    (9)游戏充值系统的开发: 需要判断账号是否存在模块,判断库存是否充足模块,充值模块。提供一个外观负责和这三个模块打交道。

    (10)生活中的外观: 找电脑组装公司组装电脑; 通过中介来处理房屋的买卖;开学需要办理各种手续流程,可以找人代为处理;公司需要开发某款产品,市场部门负责市场调研、采购部门负责采购配件,开发部门负责开发,这个时候,由总监代为处理各个部门的活动。




你可能感兴趣的:(设计模式C++)