引入外观角色之后,用户只需要直接与外观角色交互,用户与子系统之间的复杂关系由外观角色来实现,从而降低了系统的耦合度。
外观模式是一种使用频率非常高的结构型设计模式,它通过引入一个外观角色来简化客户端与子系统之间的交互,为复杂的子系统调用提供一个统一的入口,降低子系统与客户端的耦合度,且客户端调用非常方便。
外观模式又称为门面模式,它是一种对象结构型模式。外观模式是迪米特法则的一种具体实现,通过引入一个新的外观角色可以降低原有系统的复杂度,同时降低客户类与子系统的耦合度。
外观模式的目的不是给予子系统添加新的功能接口,而是为了让外部减少与子系统内多个模块的交互,松散耦合,从而让外部能够更简单地使用子系统。
外观模式的本质是:封装交互,简化调用。
根据“单一职责原则”,在软件中将一个系统划分为若干个子系统有利于降低整个系统的复杂性,一个常见的设计目标是使子系统间的通信和相互依赖关系达到最小,而达到该目标的途径之一就是引入一个外观对象,它为子系统的访问提供了一个简单而单一的入口。
外观模式也是“迪米特法则”的体现,通过引入一个新的外观类可以降低原有系统的复杂度,同时降低客户类与子系统类的耦合度。
外观模式要求一个子系统的外部与其内部的通信通过一个统一的外观对象进行,外观类将客户端与子系统的内部复杂性分隔开,使得客户端只需要与外观对象打交道,而不需要与子系统内部的很多对象打交道。
外观模式的目的在于降低系统的复杂程度。
外观模式从很大程度上提高了客户端使用的便捷性,使得客户端无须关心子系统的工作细节,通过外观角色即可调用相关功能。
public class Facade
{
private SubSystemA obj1 = new SubSystemA();
private SubSystemB obj2 = new SubSystemB();
private SubSystemC obj3 = new SubSystemC();
public void method()
{
obj1.method();
obj2.method();
obj3.method();
}
}
现在考察一个电源总开关的例子,以便进一步说明外观模式。为了使用方便,一个电源总开关可以控制四盏灯、一个风扇、一台空调和一台电视机的启动和关闭。通过该电源总开关可以同时控制上述所有电器设备,使用外观模式设计该系统
//子系统角色
public class Fan
{
private Fan(){}
private static Fan instance;
//静态代码块中创建单例对象
static {
instance=new Fan();
}
public static Fan getInstance()
{
return instance;
}
public void on()
{
System.out.println("风扇开");
}
public void off()
{
System.out.println("风扇关");
}
}
//子系统角色
public class Light
{
//静态常量
private static Light instance=new Light();;
//构造器私有化
private Light(){};
//共有静态方法,返回一个实例对象
public static Light getInstance()
{
return instance;
}
public void on()
{
System.out.println("灯开");
}
public void off()
{
System.out.println("灯关");
}
}
//外观角色
public class GeneralSwitchFaced
{
private Light light;
private Fan fan;
public GeneralSwitchFaced()
{
light=Light.getInstance();
fan=Fan.getInstance();
}
public void on()
{
light.on();
fan.on();
}
public void off()
{
light.off();
fan.off();
}
}
//测试
public class Test
{
@org.junit.Test
public void test()
{
GeneralSwitchFaced faced=new GeneralSwitchFaced();
faced.on();
faced.off();
}
}
某系统需要提供一个文件加密模块,加密流程包括三个操作,分别是读取源文件、加密、保存加密之后的文件。读取文件和保存文件使用流来实现,这三个操作相对独立,其业务代码封装在三个不同的类中。现在需要提供一个统一的加密外观类,用户可以直接使用该加密外观类完成文件的读取、加密和保存三个操作,而不需要与每一个类进行交互,使用外观模式设计该加密模块。
通过外观角色的一个方法,封装了三个独立的操作过程,即将文件的加密过程封装在了外观角色的文件加密方法中,客户通过调用该方法即可完成对文件的加密,无需挨个调用三个独立的操作
1 public class JDBCFacade {
2 private Connection conn=null;
3 private Statement statement=null;
4 public void open(String driver,String jdbcUrl,String userName,String userPwd) {
5 ......
6 }
7 public int executeUpdate(String sql) {
8 ......
9 }
10 public ResultSet executeQuery(String sql) {
11 ......
12 }
13 public void close() {
14 ......
15 }
16 }
在外观模式中,通常只需要一个外观类,并且此外观类只有一个实例,换言之它是一个单例类。在很多情况下为了节约系统资源,一般将外观类设计为单例类。当然这并不意味着在整个系统里只能有一个外观类,在一个系统中可以设计多个外观类,每个外观类都负责和一些特定的子系统交互,向用户提供相应的业务功能。
不要通过继承一个外观类在子系统中加入新的行为,这种做法是错误的。外观模式的用意是为子系统提供一个集中化和简化的沟通渠道,而不是向子系统加入新的行为,新的行为的增加应该通过修改原有子系统类或增加新的子系统类来实现,不能通过外观类来实现。
外观模式创造出一个外观对象,将客户端所涉及的属于一个子系统的协作伙伴的数量减到最少,使得客户端与子系统内部的对象的相互作用被外观对象所取代。外观类充当了客户类与子系统类之间的“第三者”,降低了客户类与子系统类之间的耦合度,外观模式就是实现代码重构以便达到“迪米特法则”要求的一个强有力的武器。
外观模式最大的缺点在于违背了“开闭原则”,当增加新的子系统或者移除子系统时需要修改外观类,可以通过引入抽象外观类在一定程度上解决该问题,客户端针对抽象外观类进行编程。对于新的业务需求,不修改原有外观类,而对应增加一个新的具体外观类,由新的具体外观类来关联新的子系统对象,同时通过修改配置文件来达到不修改源代码并更换外观类的目的。