
在真实的应用系统中,一个子系统可能由很多类组成。子系统的客户为了满足它们的需要,需要和子系统中的一些类进行交互。客户和子系统的类进行直接的交互会导致客户端对象和子系统之间高度耦合。如下图所示:
问题
在软件开发中,客户程序经常会与复杂的内部子系统之间产生耦合,而导致客户程序随着子系统的变化而变化。
那么如何简化客户程序与子系统之间的交互接口?
如何将复杂系统的内部子系统与客户程序之间的依赖解耦?
引入外观模式
外观模式为子系统提供了一个更高层次、更简单的接口,从而降低了子系统的复杂度和依赖,这使得子系统更易于使用和管理。外观是一个能为子系统和客户提供简单接口的类。当正确地应用外观时,客户不再直接与子系统中的类交互,而是与外观交互。外观承担与子系统中类交互的责任。实际上,外观是子系统与客户的接口,这样外观模式降低了子系统和客户的耦合度。 如下图:
外观对象隔离了客户和子系统对象,从而降低了耦合度。当子系统中的类进行改变时,客户端不会像以前一样受到影响。尽管客户使用由外观提供的简单接口,但是当需要的时候,客户端还是可以视外观不存在,直接访问子系统中的底层次的接口。在这种情况下,它们之间的依赖/耦合度和原来一样。
外观模式也是由代理模式发展而来的,与代理模式类似,代理模式是一对一的代理,而外观模式是一对多的代理。与装饰模式不同的是,装饰模式为对象增加功能,而外观模式则是提供一个简化的调用方式。一个系统可以有多个外观类,每个外观类都只有一个实例,可以使用单例模式实现。
一、名称:
Facade(外观模式、门面模式)
二、意图:
为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
三、应用(适用环境):
1、当你要为一个复杂子系统提供一个简单接口时。子系统往往因为不断演化而变得越来越复杂。大多数模式使用时都会产生更多更小的类。这使得子系统更具可重用性,也更容易对子系统进行定制,但这也给那些不需要定制子系统的用户带来一些使用上的困难。 Facade可以提供一个简单的缺省视图,这一视图对大多数用户来说已经足够,而那些需要更多的可定制性的用户可以越过Facade层。
2、客户程序与抽象类的实现部分之间存在着很大的依赖性。引入Facade将这个子系统与客户以及其他的子系统分离,可以提高子系统的性和可移植性。
3、当你需要构建一个层次结构的子系统时,使用Facade模式定义子系统中每层的入口点,如果子系统之间是相互依赖的,你可以让它们仅通过Facade进行通讯,从而简化了它们之间的依赖关系。
四、结构
五、Facade模式有下面一些优点:
1、它对客户屏蔽子系统组件,因而减少了客户处理的对象的数目并使得子系统使用起来更加方便。
2、它实现了子系统与客户之间的松耦合关系,而子系统内部的功能组件往往是紧耦合的。 松耦合关系使得子系统的组件变化不会影响到它的客户。Facade模式有助于建立层次结构系统,也有助于对对象之间的依赖关系分层。Facade模式可以消除复杂的循环依赖关系。这一点在客户程序与子系统是分别实现的时候尤为重要。
在大型软件系统中降低编译依赖性至关重要。在子系统类改变时,希望尽量减少重编译工作以节省时间。用Facade可以降低编译依赖性,重要系统中较小的变化所需的重编译工作。Facade模式同样也有利于简化系统在不同平台之间的移植过程,因为编译一个子系统一般不需要编译所有其他的子系统。
3、如果应用需要,它并不它们使用子系统类。因此你可以在系统易用性和通用性之间加以选择。
六、实例
外观模式中包含3种角色:目标类、外观类、客户端类。以计算机的加载和关闭过程为例,这3类角色如下。
目标类:包括CPU、内存和硬盘
外观类:即为计算机类
客户端类:即为用户
CPU类CPU.java
定义了启动和关闭CPU的函数。
package structure.facade;
public class CPU {
public void startup() {
System.out.println("启动CPU");
}
public void shutdown() {
System.out.println("关闭CPU");
}
}
内存类Memory.java
定义了加载和清空内存的函数。
package structure.facade;
public class Memory {
public void startup() {
System.out.println("加载内存");
}
public void shutdown() {
System.out.println("清空内存");
}
}
硬盘类Disk.java
定义了加载和卸载硬盘的函数。
package structure.facade;
public class Disk {
public void startup() {
System.out.println("加载硬盘");
}
public void shutdown() {
System.out.println("卸载硬盘");
}
}
外观模式类Computer.java
其中包含了CPU、内存和硬盘3个类对象,并定义了启动和关闭计算机的函数,在启动和关闭时批量地启动和关闭CPU、内存和硬盘。
package structure.facade;
public class Computer {
private CPU cpu;
private Memory memory;
private Disk disk;
public Computer() {
cpu = new CPU();
memory = new Memory();
disk = new Disk();
}
public void startup() {
System.out.println("开始启动计算机");
cpu.startup();
memory.startup();
disk.startup();
System.out.println("启动计算机完成");
}
public void shutdown() {
System.out.println("开始关闭计算机");
cpu.shutdown();
memory.shutdown();
disk.shutdown();
System.out.println("关闭计算机完成");
}
}
用户类User.java
我们只需要调用Computer.java的函数来启动和关闭计算机,而不需要依次加载和关闭CPU、内存和硬盘。
package structure.facade;
public class User {
public static void main(String args[]) {
Computer computer = new Computer();
computer.startup();
computer.shutdown();
}
}
程序输出:
开始启动计算机
启动CPU
加载内存
加载硬盘
启动计算机完成
开始关闭计算机
关闭CPU
清空内存
卸载硬盘
关闭计算机完成
从程序的输出可以看出,通过Computer一个外观类即可实现计算机所有部件的控制,而不需要单独去控制其所有的部件,这正是外观模式的作用。
在Java中,凡是为一个子系统提供统一的外部接口,而不需要了解子系统内部的类都属于外观模式的应用。这种应用场景很多,比如JDBC,它则屏蔽了与数据库进行连接、查询、更新、删除等一系列操作,因此它就属于外观模式的应用。在现在比较普遍的开源框架中,如Hibernate就可以算是一种外观模式。
七、总结
1)外观模式为复杂子系统提供了一个简单接口,并不为子系统添加新的功能和行为。
2)外观模式实现了子系统与客户之间的松耦合关系。
3)外观模式没有封装子系统的类,只是提供了简单的接口。如果应用需要,它并不客户使用子系统类。因此可以在系统易用性与通用性之间选择。
4)外观模式注重的是简化接口,它更多的时候是从架构的层次去看整个系统,而并非单个类的层次。
Facade的几个要点
从客户程序的角度来看,Facade模式不仅简化了整个组件系统的接口,同时对于组件内部与外部客户程序来说,从某种程度上也达到了一种“解耦”的效果——内部子系统的任何变化不会影响到Façade接口的变化。
Facade设计模式更注重从架构的层次去看整个系统,而不是单个类的层次。Façade很多时候更是一种架构设计模式。
Facade设计模式并非一个集装箱,可以任意地放进任何多个对象。Façade模式中组件的内部应该是“相互耦合关系比较大的一系列组件”,而不是一个简单的功能集合。
注意区分Facade模式、Adapter模式、Bridge模式与Decorator模式。Façade模式注重简化接口,Adapter模式注重转换接口,Bridge模式注重分离接口(抽象)与其实现,Decorator模式注重稳定接口的前提下为对象扩展功能。
Facade外观模式,是一种结构型模式,它主要解决的问题是:组件的客户和组件中各种复杂的子系统有了过多的耦合,随着外部客户程序和各子系统的演化,这种过多的耦合面临很多变化的挑战。在这里我想举一个例子:比如,现在有一辆汽车,我们(客户程序)要启动它,那我们就要发动引擎(子系统1),使四个车轮(子系统2)转动。但是实际中我们并不需要用手推动车轮使其转动,我们踩下油门,此时汽车再根据一些其他的操作使车轮转动。油门就好比系统给我们留下的接口,不论汽车是以何种方式转动车轮,车轮变化成什么牌子的,我们要开走汽车所要做的还是踩下油门。
GoF《设计模式》中说道:为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
Façade外观模式的结构大概是这样的:
这个图是我对Facade模式的理解,如果大家觉得有什么不对的地方欢迎给我指出。
我就上面说的那个情形写一下实现代码,首先我们要实现三个子系统(Wheel、Engine、Body):
internal class Engine
{
public string EngineWork()
{
return "BMW's Engine is Working";
}
public string EngineStop()
{
return "BMW's Engine is stoped";
}
}
internal class Wheel
{
public string WheelCircumrotate()
{
return "BMW's Wheel is Circumrotating";
}
public string WheelStop()
{
return "BMW's Wheel is stoped";
}
}
internal class Body
{
public Wheel[] wheels = new Wheel[4];
public Engine engine = new Engine();
public Body()
{
for (int i = 0; i < wheels.Length; i++)
{
wheels[i] = new Wheel();
}
}
}
然后,我们再来实现汽车的Facade
class CarFacade
{
Body body = new Body();
public void Run()
{
Console.WriteLine(body.engine.EngineWork());
for(int i = 0; i < body.wheels.Length; i++)
{
Console.WriteLine(body.wheels[i].WheelCircumrotate());
}
}
public void Stop()
{
Console.WriteLine(body.engine.EngineStop());
for (int i = 0; i < body.wheels.Length; i++)
{
Console.WriteLine(body.wheels[i].WheelStop());
}
}
}
现在我们来使用客户端程序验证一下,代码如下:
class Program
{
static void Main(string[] args)
{
CarFacade car = new CarFacade();
car.Run();
car.Stop();
Console.Read();
}
}
执行结果如下;
BMW's Engine is Working
BMW's Wheel is Circumrotating
BMW's Wheel is Circumrotating
BMW's Wheel is Circumrotating
BMW's Wheel is Circumrotating
BMW's Engine is stoped
BMW's Wheel is stoped
BMW's Wheel is stoped
BMW's Wheel is stoped
BMW's Wheel is stoped
正如上面所说:客户端代码(Program)不需要关心子系统,它只需要关心CarFacade所留下来的和外部交互的接口,而子系统是在CarFacade中聚合。
Façade模式的几个要点:
1、从客户程序的角度看,Facade模式不仅简化了整个组件系统的接口,同时对于组件内部与外部客户程序来说,从某种程度上也达到了一种“解耦”的效果——内部子系统的任何变化不会影响到Facade接口的变化。
2、Facade设计模式更注重从架构的层次去看整个系统,而不是单个类的层次。Facade很多时候更是一种架构设计模式。
