定义:
要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用(门面模式也叫做外观模式)。
示例一:门面模式(通用版)
1. 类图23-4
2. 类图说明
Subsystem Classes 是子系统所有类的简称,它可以代表几十个对象的集合。门面对象时外界访问子系统内部的唯一通道。
3. 门面模式结构图 23-5
4. 角色说明
Facade 门面角色。
客户端可以调用这个角色的方法。此角色知晓子系统的所有功能和责任。一般情况下,本角色会将所有从客户端发来的请求委派到响应的子系统去,也即该角色没有实际的业务逻辑,只是一个委托类。
subsystem 子系统角色
可以同时有一个或多个子系统。每一个子系统都不是一个单独的类,而是一个类的集合。子系统不知道门面的存在。对于子系统而言,门面仅仅是另一个客户端而已。
5. 代码清单
#include <QCoreApplication>
#include <QDebug>
#include <QVector>
//子系统
class ClassA
{
public:
void doSomethingA()
{ qDebug() << "classA do something";}
};
class ClassB
{
public:
void doSomethingB()
{ qDebug() << "classB do something";}
};
class ClassC
{
public:
void doSomethingC()
{ qDebug() << "classC do something";}
};
//门面对象
class Facade
{
public:
void methodA()
{
this->m_classA.doSomethingA();
}
void methodB()
{
this->m_classB.doSomethingB();
}
void methodC()
{
this->m_classC.doSomethingC();
}
private:
ClassA m_classA;
ClassB m_classB;
ClassC m_classC;
};
int main()
{
Facade face;
face.methodA();
face.methodB();
face.methodC();
return 0;
}
示例二:投递信件
1. 类图23-1(初版)
2. 代码清单(初版)
//写信过程接口
class ILetterProcess
{
public:
virtual void writeContext(QString context) = 0; //写信内容
virtual void fillEnvelope(QString address) = 0; //写信封
virtual void letterInotoEnvelope() = 0; //信放入信封
virtual void sendLetter() = 0; //邮递
};
//写信过程实现
class LetterProcessImpl:public ILetterProcess
{
public:
virtual void writeContext(QString context)
{
qDebug() << "write " << context;
}
virtual void fillEnvelope(QString address)
{
qDebug() << "add " << address;
}
virtual void letterInotoEnvelope()
{
qDebug() << "put the letter into envelope";
}
virtual void sendLetter()
{
qDebug() << "send Letter";
}
};
int main()
{
ILetterProcess *letterProcess = new LetterProcessImpl();
letterProcess->writeContext("aaaaa");
letterProcess->fillEnvelope("sichuan");
letterProcess->letterInotoEnvelope();
letterProcess->sendLetter();
delete letterProcess;
return 0;
}
3. 问题:
使用者需要知道写信内容,写信封,信放入信封,邮递,这四个步骤,而且还要知道他们的顺序,一旦出错,信就不可能邮寄出去。
4. 改善:
增加一个 ModenPostOffice 类,负责对一个比较复杂的信件处理过程的封装,然后高层模块只要和它有交互就行。
5. 类图23-2(改善版)
6. 代码清单(改善版)
//写信过程接口
class ILetterProcess
{
public:
virtual void writeContext(QString context) = 0; //写信内容
virtual void fillEnvelope(QString address) = 0; //写信封
virtual void letterInotoEnvelope() = 0; //信放入信封
virtual void sendLetter() = 0; //邮递
};
//写信过程实现
class LetterProcessImpl:public ILetterProcess
{
public:
virtual void writeContext(QString context)
{
qDebug() << "write " << context;
}
virtual void fillEnvelope(QString address)
{
qDebug() << "add " << address;
}
virtual void letterInotoEnvelope()
{
qDebug() << "put the letter into envelope";
}
virtual void sendLetter()
{
qDebug() << "send Letter";
}
};
class ModenPostOffice
{
public:
ModenPostOffice()
{
this->m_letterProcess = new LetterProcessImpl();
}
~ModenPostOffice()
{
delete this->m_letterProcess;
}
void sendLetter(QString context, QString address)
{
this->m_letterProcess->writeContext(context);
this->m_letterProcess->fillEnvelope(address);
this->m_letterProcess->letterInotoEnvelope();
this->m_letterProcess->sendLetter();
}
private:
ILetterProcess *m_letterProcess;
};
int main()
{
ModenPostOffice office;
QString address = "sichuan";
QString context = "aaaa";
office.sendLetter(context, address);
return 0;
}
扩展性提高了,例:增加一项安全检查,增加了一个Police类,负责对信件进行检查
7. 类图23-3(扩展版)
8. 代码清单(扩展版)
//写信过程接口
class ILetterProcess
{
public:
virtual void writeContext(QString context) = 0; //写信内容
virtual void fillEnvelope(QString address) = 0; //写信封
virtual void letterInotoEnvelope() = 0; //信放入信封
virtual void sendLetter() = 0; //邮递
};
//写信过程实现
class LetterProcessImpl:public ILetterProcess
{
public:
virtual void writeContext(QString context)
{
qDebug() << "write " << context;
}
virtual void fillEnvelope(QString address)
{
qDebug() << "add " << address;
}
virtual void letterInotoEnvelope()
{
qDebug() << "put the letter into envelope";
}
virtual void sendLetter()
{
qDebug() << "send Letter";
}
};
class Police
{
public:
void checkLetter(ILetterProcess *letterProcess)
{
qDebug() << "is safe";
}
};
class ModenPostOffice
{
public:
ModenPostOffice()
{
this->m_letterProcess = new LetterProcessImpl();
}
~ModenPostOffice()
{
delete this->m_letterProcess;
}
void sendLetter(QString context, QString address)
{
this->m_letterProcess->writeContext(context);
this->m_letterProcess->fillEnvelope(address);
this->m_letterPolice.checkLetter(this->m_letterProcess);
this->m_letterProcess->letterInotoEnvelope();
this->m_letterProcess->sendLetter();
}
private:
ILetterProcess *m_letterProcess;
Police m_letterPolice;
};
int main()
{
ModenPostOffice office;
QString address = "sichuan";
QString context = "aaaa";
office.sendLetter(context, address);
return 0;
}
三、门面模式的应用
1. 优点:
- 减少系统的相互依赖。如果不使用门面模式,外界访问直接深入到子系统内部,相互之间是强耦合关系。门面模式使得所有的依赖都是对门面对象依赖,与子系统无关。
- 提高了灵活性。不论子系统内部如何变化,只要不影响到门面对象即可。
- 提高安全性。想让外部访问子系统的哪些业务就开通哪些逻辑,不在门面上开通的方法,无法访问到。
2. 缺点:
对修改关闭,对扩展开放。一旦系统投产后发现有错误,唯一能做的就是修改门面角色的代码,这个风险相当大,需要在设计的时候慎重思考。
3. 使用场景:
- 为一个复杂的模块或子系统提供一个供外界访问的接口。
- 子系统相对独立。外界对子系统的访问只要黑箱操作即可。例:利息计算问题,对于使用该系统的开发人员来说,需要做的是输入金额及存期,其他不用关心,返回结果是利息,这时候,门面模式非用不可。
- 预防低水平人员带来的风险扩散。当使用低水平的技术人员参与项目开发时,为降低个人代码质量对整体项目的影响风险,一般做法“画地为牢”,只能在指定的子系统中开发,然后再提供门面接口进行访问操作。
4. 注意事项:
①一个子系统可以有多个门面。
- 一般情况下,一个子系统只要一个门面就够了,以下几种情况需要子系统有多个门面:
- 门面已经庞大到不能承受的程度。例:一个门面对象已经超过了200行代码,虽然都是简单的委托,也建议拆分成多个门面,可按照功能拆分。比如数据库操作门面可拆分为,查询门面、删除门面、更新门面等。
- 子系统可以提供不同访问路径。
②门面不参与子系统内的业务逻辑
四、最佳实践
当一个子系统比较复杂时,比如算法或者业务比较复杂,就可以封装出一个或多个门面出来,项目的结构简单,而且扩展性非常好。另外,对于一个较大项目,为了避免人员带来的风险,也可以使用门面模式,技术水平较差的成员,尽量安排独立的模块,然后把他写的程序封装到一个门面里,尽量让其他项目成员不用看到这些人的代码。使用门面模式后,对门面进行单元测试,约束项目成员的代码质量。
参考文献《秦小波. 设计模式之禅》(第2版) (华章原创精品) 机械工业出版社