设计模式之门面模式

门面模式

门面模式

门面模式(Facade Pattern)又叫外观模式,提供了一个统一的接口,用来访问子系统中的一群接口。其主要特征是定义了一个高层接口,让子系统更容易使用,属于结构性模式。

其实,在日常的编码工作中,我们都在有意无意地大量使用门面模式,但凡只要高层模块需要调度多个子系统(2个以上类对象),我们都会自觉地创建一个新类封装这些子系统,提供精简接口,让高层模块可以更加容易间接调用这些子系统的功能。尤其是现阶段各种第三方SDK,各种开源类库,很大概率都会使用门面模式。尤其是你觉得调用越方便的,门面模式使用的一般更多。

门面模式的应用场景

1、子系统越来越复杂,增加门面模式提供简单接口
2、构建多层系统结构,利用门面对象作为每层的入口,简化层间调用。

门面模式的通用写法

UML图

门面模式主要包含2种角色:
外观角色(Facade):也称门面角色,系统对外的统一接口;
子系统角色(SubSystem):可以同时有一个或多个SubSystem。每个SubSytem都不是一个单独的类,而是一个类的集合。SubSystem并不知道Facade 的存在,对于SubSystem而言,Facade只是另一个客户端而已(即Facade对SubSystem透明)。

下面是门面模式的通用代码,首先分别创建3个子系统的业务逻辑SubSystemA, SubSystemB, SubSystemC,代码很简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class SubSystemA
{
public void doA()
{
System.out.println("do A");
}
}
public class SubSystemB
{
public void doB()
{
System.out.println("do B");
}
}
public class SubSystemC
{
public void doC()
{
System.out.println("do C");
}
}

创建外观角色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
{
private SubSystemA subSystemA = new SubSystemA();
private SubSystemB subSystemB = new SubSystemB();
private SubSystemC subSystemC = new SubSystemC();

public void doA()
{
subSystemA.doA();
}

public void doB()
{
subSystemB.doB();
}

public void doC()
{
subSystemC.doC();
}
}

测试代码

1
2
3
4
5
6
7
8
9
10
public class Main
{
public static void main(String[] args)
{
Facade facade = new Facade();
facade.doA();
facade.doB();
facade.doC();
}
}

门面模式在源码中的应用

门面模式在源码中的应用,先来看Spring JDBC模块下的JdbcUtils类,它封装了和JDBC相关的所有操作,它一个代码片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public static void closeConnection(@Nullable Connection con) {
if (con != null) {
try {
con.close();
}
catch (SQLException ex) {
logger.debug("Could not close JDBC Connection", ex);
}
catch (Throwable ex) {
// We don't trust the JDBC driver: It might throw RuntimeException or Error.
logger.debug("Unexpected exception on closing JDBC Connection", ex);
}
}
}

/**
* Close the given JDBC Statement and ignore any thrown exception.
* This is useful for typical finally blocks in manual JDBC code.
* @param stmt the JDBC Statement to close (may be {@code null})
*/
public static void closeStatement(@Nullable Statement stmt) {
if (stmt != null) {
try {
stmt.close();
}
catch (SQLException ex) {
logger.trace("Could not close JDBC Statement", ex);
}
catch (Throwable ex) {
// We don't trust the JDBC driver: It might throw RuntimeException or Error.
logger.trace("Unexpected exception on closing JDBC Statement", ex);
}
}
}

/**
* Close the given JDBC ResultSet and ignore any thrown exception.
* This is useful for typical finally blocks in manual JDBC code.
* @param rs the JDBC ResultSet to close (may be {@code null})
*/
public static void closeResultSet(@Nullable ResultSet rs) {
if (rs != null) {
try {
rs.close();
}
catch (SQLException ex) {
logger.trace("Could not close JDBC ResultSet", ex);
}
catch (Throwable ex) {
// We don't trust the JDBC driver: It might throw RuntimeException or Error.
logger.trace("Unexpected exception on closing JDBC ResultSet", ex);
}
}
}

基本都是封装了jdbc的一些操作

在Tomcat的源码中也有体现,举个例子RequestFacade类,来看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class RequestFacade {
···
@Override
public Object getAttribute(String name) {

if (request == null) {
throw new IllegalStateException(
sm.getString("requestFacade.nullRequest"));
}

return request.getAttribute(name);
}


@Override
public Enumeration<String> getAttributeNames() {

if (request == null) {
throw new IllegalStateException(
sm.getString("requestFacade.nullRequest"));
}

if (Globals.IS_SECURITY_ENABLED){
return AccessController.doPrivileged(
new GetAttributePrivilegedAction());
} else {
return request.getAttributeNames();
}
}


@Override
public String getCharacterEncoding() {

if (request == null) {
throw new IllegalStateException(
sm.getString("requestFacade.nullRequest"));
}

if (Globals.IS_SECURITY_ENABLED){
return AccessController.doPrivileged(
new GetCharacterEncodingPrivilegedAction());
} else {
return request.getCharacterEncoding();
}
}


@Override
public void setCharacterEncoding(String env)
throws java.io.UnsupportedEncodingException {

if (request == null) {
throw new IllegalStateException(
sm.getString("requestFacade.nullRequest"));
}

request.setCharacterEncoding(env);
}
···
}

看名字就知道它用了门面模式。它封装了非常多的request的操作,也整合了很多servlet-api以外的一些内容,给用户使用提供了很大便捷。同样,Tomcat对Response和Session当也封装了ResponseFacade和StandardSessionFacade类。

门面模式的优缺点

优点:

1、简化了调用过程,无需深入了解子系统,以防给子系统带来风险

2、减少系统依赖、松散耦合

3、更好地划分访问层次,提高了安全性

4、遵循迪米特法则,即最少知道原则。

缺点:

1、当增加子系统和扩展子系统行为时,可能容易带来未知风险

2、不符合开闭原则

3、某些情况下可能违背单一职责原则。

打赏

请我喝杯咖啡吧~

支付宝
微信