总目录
前言
在软件开发过程中,要完成一个功能,可能需要调用很多接口,不仅增加了代码间的耦合度,也增加了调试成本和维护的复杂度。不如我们把这些接口再封装一次,给一个很好的“外观”,让使用者使用更方便,只需调用一个接口,就可以完成以前调用多个接口的来完成任务,相信大家对这种编码技巧并不陌生,这就是本文将介绍的外观模式。
1 基础介绍
-
外观模式(Facade Pattern)也被称为 门面模式
-
来个图,直观的了解下
原本客户端需要直接调用多个不同子系统的接口,调用关系混乱,代码耦合度高,使用了外观模式后,所有的客户端只需和子系统的 门面 facade 打交道,子系统也只需和facade打交道,不仅捋顺了各个系统之间的关系,也减低了代码间的耦合度。 -
定义:为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
-
使用外观模式时,我们创建了一个统一的类,用来包装子系统中一个或多个复杂的类,客户端可以直接通过外观类来调用内部子系统中方法,从而外观模式让客户和子系统之间避免了紧耦合。
-
外观模式包含如下两个角色:
-
外观角色(Facade):在客户端可以调用它的方法,在外观角色中可以知道相关的(一个或者多个)子系统的功能和责任;在正常情况下,它将所有从客户端发来的请求委派到相应的子系统去,传递给相应的子系统对象处理。
-
子系统角色(SubSystem):在软件系统中可以有一个或者多个子系统角色,每一个子系统可以不是一个单独的类,而是一个类的集合,它实现子系统的功能;每一个子系统都可以被客户端直接调用,或者被外观角色调用,它处理由外观类传过来的请求;子系统并不知道外观的存在,对于子系统而言,外观角色仅仅是另外一个客户端而已。
-
-
使用分析:
-
一个系统可以有多个门面类
在门面模式中,通常只需要一个门面类,并且此门面类只有一个实例,换言之它是一个单例类。当然这并不意味着在整个系统里只有一个门面类,而仅仅是说对每一个子系统只有一个门面类。或者说,如果一个系统有好几个子系统的话,每一个子系统都有一个门面类,整个系统可以有数个门面类。 -
为子系统增加新行为
初学者往往以为通过继承一个门面类便可在子系统中加入新的行为,这是错误的。门面模式的用意是为子系统提供一个集中化和简化的沟通管道,而不能向子系统加入新的行为。比如医院中的接待员并不是医护人员,接待员并不能为病人提供医疗服务。 -
Facade有助于建立层次结构的系统,实现了子系统与客户之间的松耦合关系,子系统内部的功能组件往往是紧耦合的。松耦合关系使得子系统的组件变化不会影响到它的客户。Facade消除了复杂的循环依赖关系。这一点在客户程序与子系统分别实现的时候格外重要。
-
从客户程序的角度来看,Facade模式不仅简化了整个组件系统的接口,同时对于组件内部与外部客户程序来说,从某种程度上也达到了一种“解耦”的效果——内部子系统的任何变化不会影响到Facade接口的变化。
-
2 使用场景
- 适用于一些有多个子系统,需要为这些复杂的系统提供一个统一接口的软件系统
- 适用于存在多个子系统且需要保持子系统的独立性的时候
- 在层次化结构中,可以使用外观模式定义系统中每一层的入口。其中三层架构就是这样的一个例子。
3 实现方式
以我们乘坐高铁为例,一般我们必须做以下步骤:
- 购买高铁票
- 过安检
- 过检票口,进入高铁站,从站内乘坐高铁
1 当我们不使用外观模式的时候
//车票子系统
public class TicketsSys
{
//售票方法
public void SaleTicket()
{
Console.WriteLine("售票系统 - 已售出高铁票一张");
}
}
//安检系统
public class SecurityCheckSys
{
//安检方法
public void SecurityCheck()
{
Console.WriteLine("安检系统 - 已完成安检");
}
}
//检票系统
public class TicketCheckSys
{
public void TicketCheck()
{
Console.WriteLine("检票系统 - 已完成检票");
}
}
客户端调用各个子系统,实现乘坐高铁
static void Main(string[] args)
{
//1 先购买高铁票
TicketsSys ticketsSys = new TicketsSys();
ticketsSys.SaleTicket();
//2 再进行安检
SecurityCheckSys securityCheckSys = new SecurityCheckSys();
securityCheckSys.SecurityCheck();
//3 再检票
TicketCheckSys ticketCheckSys = new TicketCheckSys();
ticketCheckSys.TicketCheck();
//4 最后 乘坐高铁
Console.WriteLine("欢迎乘坐高铁,祝您旅途愉快!");
Console.ReadKey();
}
在上面的额案例中,客户端必须同时保存售票系统,安检系统,检票系统的引用,如果这些子系统发生改变时,此时客户端的调用代码也要随之改变,这样就没有很好的可扩展性。那我们看看外观模式是如何解决这个问题的。
2 使用外观模式时
// 外观类 或 门面类
//外观模式的核心类
public class FacadeSys
{
private TicketsSys ticketsSys;
private SecurityCheckSys securityCheckSys;
private TicketCheckSys ticketCheckSys;
public FacadeSys()
{
this.ticketsSys = new TicketsSys();
this.securityCheckSys = new SecurityCheckSys();
this.ticketCheckSys = new TicketCheckSys();
}
//乘坐高铁
public void TakeHighspeedRail()
{
//1 先购买高铁票
ticketsSys.SaleTicket();
//2 再进行安检
securityCheckSys.SecurityCheck();
//3 再检票
ticketCheckSys.TicketCheck();
//4 最后 乘坐高铁
Console.WriteLine("欢迎乘坐高铁,祝您旅途愉快!");
}
}
//车票子系统
public class TicketsSys
{
//售票方法
public void SaleTicket()
{
Console.WriteLine("售票系统 - 已售出高铁票一张");
}
}
//安检系统
public class SecurityCheckSys
{
//安检方法
public void SecurityCheck()
{
Console.WriteLine("安检系统 - 已完成安检");
}
}
//检票系统
public class TicketCheckSys
{
public void TicketCheck()
{
Console.WriteLine("检票系统 - 已完成检票");
}
}
客户端调用,实现乘坐高铁
static void Main(string[] args)
{
FacadeSys facadeSys = new FacadeSys();
facadeSys.TakeHighspeedRail();
Console.ReadKey();
}
使用了外观模式之后,客户端只依赖与外观类,从而将客户端与子系统的依赖解耦了,如果子系统发生改变,此时客户端的代码并不需要去改变。外观模式的实现核心主要是:由外观类去保存各个子系统的引用,实现由一个统一的外观类去包装多个子系统类,然而客户端只需要引用这个外观类,然后由外观类来调用各个子系统中的方法。
外观模式的实现方式和适配器模式非常类似,然而外观模式与适配器模式不同的是:适配器模式是将一个对象包装起来以改变其接口,而外观是将一群对象 ”包装“起来以简化其接口。它们的意图是不一样的,适配器是将接口包装获得适配,而外观模式是提供一个统一的接口来简化接口。
4 优缺点分析
-
优点
- 外观模式对客户屏蔽了子系统组件,从而简化了接口,减少了客户处理的对象数目并使子系统的使用更加简单。
- 外观模式实现了子系统与客户之间的松耦合关系,而子系统内部的功能组件是紧耦合的。松耦合使得子系统的组件变化不会影响到它的客户。
-
缺点
- 如果增加新的子系统可能需要修改外观类或客户端的源代码,这样就违背了”开——闭原则“(不过这点也是不可避免)。
结语
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。
参考资料:
C#设计模式之十外观模式(Facade Pattern)【结构型】
C#设计模式(11)——外观模式(Facade Pattern)