9.3.7 门面模式

9.3.7 门面模式

随着系统的不断改进和开发,它们会变得越来越复杂,系统会生成大量的类,这使得程序流程更难被理解。门面模式可为这些类提供一个简化的接口,从而简化访问这些类的复杂性,有时这种简化可能降低访问这些底层类的灵活性,但除了要求特别苛刻的客户端之外,它通常都可以提供所需的全部功能,当然,那些苛刻的用户仍然可以直接访问底层的类和方法。
门面模式( Facade)也被称为正面模式、外观模式,这种模式用于将一组复杂的类包装到一个简单的外部接口中。
现在考虑这样的场景:有一个顾客需要到饭店用餐,这就需要定义一个Customer类,并为该类定义一个haveDinner()方法。考虑该饭店有三个部门:收银部、厨师部和服务生部,用户就餐需要这三个部门协调才能完成。

程序示例

本示例程序先定义一个收银部,用户需要调用该部门的pay()方法来支付用餐费

1
2
3
4
5
6
7
8
9
10
public class PaymentImpl implements Payment
{
// 实现模拟顾客支付费用的方法
public String pay()
{
String food = "快餐";
System.out.println("你已经向收银员支付了费用,您购买的食物是:" + food);
return food;
}
}

程序接下来要定义一个厨师部门,用户需要调用该部门的cook()方法来烹调食物。

1
2
3
4
5
6
7
8
9
public class CookImpl implements Cook
{
// 实现模拟烹调食物的方法
public String cook(String food)
{
System.out.println("厨师正在烹调:" + food);
return food;
}
}

程序还要定义一个服务生部门,用户需要调用该部门的serve()方法来得到食物

1
2
3
4
5
6
7
8
public class WaiterImpl implements Waiter
{
// 模拟服务生上菜的方法
public void serve(String food)
{
System.out.println("服务生已将" + food + "端过来了,请慢用...");
}
}

接下来实现Customer类的haveDinner()方法时,系统将有如下代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Customer
{
public void haveDinner()
{
// // 依次创建三个部门实例
Payment pay = new PaymentImpl();
Cook cook = new CookImpl();
Waiter waiter = new WaiterImpl();
// 依次调用三个部门实例的方法来实现用餐功能
String food = pay.pay();
food = cook.cook(food);
waiter.serve(food);
}
public static void main(String[] args)
{
new Customer().haveDinner();
}
}

正如上面的代码所示, Customer需要依次调用三个部门的方法才可实现这个havaDinner()方法。实际上,如果这个饭店有更多的部门,那么程序就需要调用更多部门的方法来实现这个haveDinner()方法—这就会增加haveDinner()方法的实现难度了
为了解决这个问题,可以为PaymentCookWaiter三个部门提供一个门面(Facade),使用该Facade来包装这些类,对外提供一个简单的访问方法。下面是该Facade类的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Facade
{
// 定义被Facade封装的三个部门
Payment pay;
Cook cook;
Waiter waiter;
// 构造器
public Facade()
{
this.pay = new PaymentImpl();
this.cook = new CookImpl();
this.waiter = new WaiterImpl();
}
public void serveFood()
{
// 依次调用三个部门的方法,封装成一个serveFood()方法
String food = pay.pay();
food = cook.cook(food);
waiter.serve(food);
}
}

Facade代码可以看出,该门面类保证了PaymentCookWaiter三个部门,程序的粗体字代码对外提供了一个简单的serveFood方法,该方法对外提供了一个用餐的方法,而底层则依赖于三个部门的pay()cook()serve()三个方法。
一旦程序提供了这个门面类Facade之后, Cutsomer类实现haveDinner()方法就变得更加简单了下面是通过Facade类实现haveDinney()方法的代码。

1
2
3
4
5
6
public void haveDinner()
{
// 直接依赖于Facade类来实现用餐方法
Facade f = new Facade();
f.serveFood();
}

从上面的程序可以看出,如果不采用门面模式,客户端需要自行决定需要调用哪些类、哪些方法,并需要按合理的顺序来调用它们才可实现所需的功能。不采用门面模式时,程序有如图9.8所示的结构。
这里有一张图片
从图9.8中可以看出,两个客户端需要和底层各对象形成错综复杂的网络调用,无疑增加了客户端编程的复杂度。使用门面模式后的程序结构如图9.9所示。
这里有一张图片
从图9.9可以看出,当程序使用了门面模式之后,客户端代码只需要和门面类进行交互,客户端代码变得极为简单。
阅读到此处相信读者对SpringHibernateTemplate类有点感觉了,当程序使用HibernateTemplatefindo方法时,程序只要此一行代码即可得到查询返回的List。但实际上该find()方法后隐藏了如下代码:

1
2
3
4
5
6
7
Session session=sf.openSession();
Query query=session.createQuery(hql);
for(int i=0 ;i<args.length;i++)
{
query.setParameter(i +"",args[i]);
}
query.list();

因此可以认为Hibernate TemplateSessionFactorySessionQuery等类的门面,当客户端程序需要进行持久化査询时,程序无须调用这些类,而是直接调用HibernateTemplate门面类的方法即可。
除此之外, Java EE应用里使用业务逻辑组件来封装DAO组件也是典型的门面模式,每个业务逻辑组件都是众多DAO组件的门面,系统的控制器类无须直接访问DAO组件,而是由业务逻辑方法来组合多个DAO方法以完成所需功能,而Action只需与业务逻辑组件交互即可。在这种设计方式下, Java EE应用的各组件有如图9.10所示的结构。
这里有一张图片