概念
看到"门面"这个词,大家一定都觉得很熟悉。日常生活中的"门面"就是我们买东西的地方,它跟各种商品的生产商打交道,收集商品后再卖给我们。如果没有"门面",我们将不得不直接跟各种各样的生产商买商品。
Facade 模式正是这样一个"门面":我们本来需要与后台的多个类或接口打交道,而 Facade 模式在客户端和后台之间插入一个中间层——门面,这个门面跟后台的多个类或接口打交道,客户端只需要跟门面打交道即可。
Facade 类是一个简化的用户接口,它和后台中的多个类产生依赖关系,而客户类则只跟 Facade 类产生依赖关系。后台的开发者熟悉自己开发的各个类,容易解决与多个类的依赖关系;而使用者不太熟悉后台的各个类,通过 Facade 可以大大降低使用难度。
情况一:功能分布在多个无关类中
客户类要使用的功能分布在多个类中,客户必须先初始化各个类才能使用。这时适合将这些功能集中在一个 Facade 类里,同时替用户做初始化工作。
场景: 商店里出售三种商品——衣服、电脑和手机,分别由各自的生产厂商提供。
各厂商类:
public class CoatFactory {
public Coat saleCoat() {
// ...
return coat;
}
}
public class ComputerFactory {
public Computer saleComputer() {
// ...
return computer;
}
}
public class MobileFactory {
public Mobile saleMobile() {
// ...
return mobile;
}
}
没有商店时,客户需要分别跟各厂商打交道:
// 买衣服
CoatFactory coatFactory = new CoatFactory();
coatFactory.saleCoat();
// 买电脑
ComputerFactory computerFactory = new ComputerFactory();
computerFactory.saleComputer();
// 买手机
MobileFactory mobileFactory = new MobileFactory();
mobileFactory.saleMobile();
对客户来说,和这么多厂家类打交道显然很麻烦。
引入 Facade(商店类),让商店和厂家打交道,客户只和商店打交道:
public class Store {
public Coat saleCoat() {
CoatFactory coatFactory = new CoatFactory();
return coatFactory.saleCoat();
}
public Computer saleComputer() {
ComputerFactory computerFactory = new ComputerFactory();
return computerFactory.saleComputer();
}
public Mobile saleMobile() {
MobileFactory mobileFactory = new MobileFactory();
return mobileFactory.saleMobile();
}
}
客户端调用:
Store store = new Store();
store.saleCoat();
store.saleComputer();
store.saleMobile();
情况二:完成某个功能需要调用后台多个类
客户要完成某个功能,需要调用后台多个类才能实现,这时尤其应该使用 Facade 模式。
反例: 后台开发人员强迫使用者自己写这样的代码:
String xmlString = null;
try {
xmlString = gdSizeChart.buildDataXML(incBean);
String path = "D:/workspace/gridfile.xml";
File f = new File(path);
PrintWriter out = new PrintWriter(new FileWriter(f));
out.print(xmlString);
out.close();
request.setAttribute("xmlString", xmlString);
} catch (Exception ex) {
ex.printStackTrace();
}
使用者不了解后台思路,不知道来龙去脉,这样的调用方式很困难。
改进: 引入 Facade 类,把不该由客户类做的事封装起来:
public class Facade {
public static void doAll(PE_MeasTableExdBean incBean, HttpServletRequest request) {
// ... 其他调用 ...
request.setAttribute("xmlString", Facade.getFromOut(incBean));
}
private static String getFromOut(PE_MeasTableExdBean incBean) {
try {
String xmlString = gdSizeChart.buildDataXML(incBean);
String path = "D:/workspace/gridfile.xml";
File f = new File(path);
PrintWriter out = new PrintWriter(new FileWriter(f));
out.print(xmlString);
out.close();
return xmlString;
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
}
客户端调用:
Facade.doAll(incBean, request);
注意:
getFromOut方法放在 Facade 类中是为了示例简洁,实际上违反了单一职责原则,应单独抽取。
总结模式结构
后台有多个类实现某个功能:
public class ClassA {
public void doA() { /* ... */ }
}
public class ClassB {
public void doB() { /* ... */ }
}
public class ClassC {
public void doC() { /* ... */ }
}
没有 Facade 时客户需要:
ClassA a = new ClassA();
a.doA();
ClassB b = new ClassB();
b.doB();
ClassC c = new ClassC();
c.doC();
引入 Facade:
public class Facade {
public void doAll() {
new ClassA().doA();
new ClassB().doB();
new ClassC().doC();
}
}
客户端:
Facade facade = new Facade();
facade.doAll();
现实中的 Facade:企业 Portal
企业内部通常有很多应用系统——OA、HR、财务、CRM 等,不同系统完成不同功能。企业一般会做一个 Portal 页面,用户在这里可以选择进入哪个子系统,不需要记忆各系统的 URL。
这正是 Facade 模式在产品层面的体现:
| 角色 | 对应 |
|---|---|
| Client | 用户 |
| Facade | Portal 页面 |
| Subsystems | OA、HR、财务等各子系统 |
不过 Portal 更多是 UI 层面的"门面",代码中的 Facade 通常还会做调用编排——不只是提供入口列表,还会把多个子系统的调用封装成一个简单方法(如先调 A,再调 B,最后调 C)。两者概念一脉相承,Portal 是 Facade 思想在系统设计层面的自然延伸。