Bootstrap

C# 设计模式之外观模式

总目录


前言

在软件开发过程中,要完成一个功能,可能需要调用很多接口,不仅增加了代码间的耦合度,也增加了调试成本和维护的复杂度。不如我们把这些接口再封装一次,给一个很好的“外观”,让使用者使用更方便,只需调用一个接口,就可以完成以前调用多个接口的来完成任务,相信大家对这种编码技巧并不陌生,这就是本文将介绍的外观模式。


1 基础介绍

  1. 外观模式(Facade Pattern)也被称为 门面模式

  2. 来个图,直观的了解下
    在这里插入图片描述
    原本客户端需要直接调用多个不同子系统的接口,调用关系混乱,代码耦合度高,使用了外观模式后,所有的客户端只需和子系统的 门面 facade 打交道,子系统也只需和facade打交道,不仅捋顺了各个系统之间的关系,也减低了代码间的耦合度。

  3. 定义:为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

  4. 使用外观模式时,我们创建了一个统一的类,用来包装子系统中一个或多个复杂的类,客户端可以直接通过外观类来调用内部子系统中方法,从而外观模式让客户和子系统之间避免了紧耦合。

  5. 外观模式包含如下两个角色:

    • 外观角色(Facade):在客户端可以调用它的方法,在外观角色中可以知道相关的(一个或者多个)子系统的功能和责任;在正常情况下,它将所有从客户端发来的请求委派到相应的子系统去,传递给相应的子系统对象处理。

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

  6. 使用分析:

    • 一个系统可以有多个门面类
      在门面模式中,通常只需要一个门面类,并且此门面类只有一个实例,换言之它是一个单例类。当然这并不意味着在整个系统里只有一个门面类,而仅仅是说对每一个子系统只有一个门面类。或者说,如果一个系统有好几个子系统的话,每一个子系统都有一个门面类,整个系统可以有数个门面类。

    • 为子系统增加新行为
      初学者往往以为通过继承一个门面类便可在子系统中加入新的行为,这是错误的。门面模式的用意是为子系统提供一个集中化和简化的沟通管道,而不能向子系统加入新的行为。比如医院中的接待员并不是医护人员,接待员并不能为病人提供医疗服务。

    • 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)

;