定义:
它是一种通过为多个复杂的子系统提供一个一致的接口,而使得这些子系统更加容易被访问的设计模式。
优点:
1、降低了子系统与客户端之间的耦合性,使得子系统的变化不会影响调用它的客户类。(“迪米特法则”的典型应用)
2、对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
3、降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。
缺点:
1、不能很好的限制客户使用子系统类。
2、增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
外观模式的结构:
1、外观(Facade)角色:为多个子系统对外提供一个共同的接口。
2、子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色来访问它。
3、客户(Client)角色:通过一个外观角色访问各个系统的功能。
示例:
每个人都需要吃饭,你可以自己买菜做饭,也可以去饭店吃饭。要是自己买菜做饭,想吃宫保鸡丁、糖醋里脊、地三鲜,那么你就需要买这两个菜所需要的食材,然后到家里自己炒。如果你比较懒,或者不会做饭,那么你就需要去饭店吃饭(其实去饭店吃饭完全可以看成一个外观模式),你只需要告诉漂亮的女服务员你想吃宫保鸡丁、糖醋里脊、地三鲜,那么你就可以在座位上等着了,完全不用操心谁去买原材料、谁去做菜,只等着吃就行了。
子系统角色1代码如下:
/*子角色系统1,可以做宫保鸡丁*/
public class SubSystem_GongBaoJiDing {
public void method1() {
System.out.println("我是厨师1号,我需要做宫保鸡丁!");
}
}
子系统角色2代码如下:
/*子角色系统2,可以做糖醋里脊*/
public class SubSystem_TangCuLiJi {
public void method2() {
System.out.println("我是厨师2号,我需要做糖醋里脊");
}
}
子系统角色3代码如下:
/*子角色系统3,可以做地三鲜*/
public class SubSystem_DiSanXian {
public void method3() {
System.out.println("我是厨师3号,我需要做地三鲜");
}
}
外观角色代码如下:
/*外观角色,吃什么告诉服务员就可以了*/
public class Facade_FuWuYuan {
private SubSystem_GongBaoJiDing gongBaoJiDing = new SubSystem_GongBaoJiDing();
private SubSystem_TangCuLiJi tangCuLiJi= new SubSystem_TangCuLiJi();
private SubSystem_DiSanXian DiSanXian = new SubSystem_DiSanXian();
public void method() {
gongBaoJiDing.method1();
tangCuLiJi.method2();
DiSanXian.method3();
}
}
客户角色代码如下:
/*客户角色*/
public class Client {
public static void main(String[] args) {
Facade_FuWuYuan fuWuYuan = new Facade_FuWuYuan();
fuWuYuan.method();
}
}
程序运行结果如下:
外观模式的结构图:
如图2所示,这个基本上就是外观模式的结构图了,接下来用精简的代码来简单实现下:
/*子系统1*/
public class SubSystem_01 {
public void method1() {
System.out.println("method1");
}
}
/*子系统2*/
public class SubSystem_02 {
public void method2() {
System.out.println("method2");
}
}
/*子系统3*/
public class SubSystem_03 {
public void method3() {
System.out.println("method3");
}
}
/*外观角色*/
public class Facade {
private SubSystem_01 subSystem_01 = new SubSystem_01();
private SubSystem_02 subSystem_02 = new SubSystem_02();
private SubSystem_03 subSystem_03 = new SubSystem_03();
public void method() {
subSystem_01.method1();
subSystem_02.method2();
subSystem_03.method3();
}
}
/*客户端角色*/
public class Client {
public static void main(String[] args) {
Facade facade = new Facade();
facade.method();
}
}
输入结果如下:
源码分析外观模式的典型应用:
1、spring jdbc中的外观模式
查看 org.springframework.jdbc.support.JdbcUtils
,该工具类主要是对原生的jdbc进行封装
public abstract class JdbcUtils {
public static void closeConnection(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);
}
}
}
public static Object getResultSetValue(ResultSet rs, int index, Class<?> requiredType) throws SQLException {
if (requiredType == null) {
return getResultSetValue(rs, index);
}
Object value = null;
boolean wasNullCheck = false;
// Explicitly extract typed value, as far as possible.
if (String.class.equals(requiredType)) {
value = rs.getString(index);
}
else if (boolean.class.equals(requiredType) || Boolean.class.equals(requiredType)) {
value = rs.getBoolean(index);
wasNullCheck = true;
}
else if (byte.class.equals(requiredType) || Byte.class.equals(requiredType)) {
value = rs.getByte(index);
wasNullCheck = true;
}
else if (short.class.equals(requiredType) || Short.class.equals(requiredType)) {
value = rs.getShort(index);
wasNullCheck = true;
}
else if (int.class.equals(requiredType) || Integer.class.equals(requiredType)) {
value = rs.getInt(index);
wasNullCheck = true;
}
else if (long.class.equals(requiredType) || Long.class.equals(requiredType)) {
value = rs.getLong(index);
wasNullCheck = true;
}
else if (float.class.equals(requiredType) || Float.class.equals(requiredType)) {
value = rs.getFloat(index);
wasNullCheck = true;
}
else if (double.class.equals(requiredType) || Double.class.equals(requiredType) ||
Number.class.equals(requiredType)) {
value = rs.getDouble(index);
wasNullCheck = true;
}
else if (byte[].class.equals(requiredType)) {
value = rs.getBytes(index);
}
else if (java.sql.Date.class.equals(requiredType)) {
value = rs.getDate(index);
}
else if (java.sql.Time.class.equals(requiredType)) {
value = rs.getTime(index);
}
else if (java.sql.Timestamp.class.equals(requiredType) || java.util.Date.class.equals(requiredType)) {
value = rs.getTimestamp(index);
}
else if (BigDecimal.class.equals(requiredType)) {
value = rs.getBigDecimal(index);
}
else if (Blob.class.equals(requiredType)) {
value = rs.getBlob(index);
}
else if (Clob.class.equals(requiredType)) {
value = rs.getClob(index);
}
else {
// Some unknown type desired -> rely on getObject.
value = getResultSetValue(rs, index);
}
if (wasNullCheck && value != null && rs.wasNull()) {
value = null;
}
return value;
}
// ...省略...
}
2、Mybatis中的外观模式
查看 org.apache.ibatis.session.Configuration
类中以 new
开头的方法,该类主要是对一些创建对象的操作进行封装
public class Configuration {
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement,
RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new
DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
// ...省略...
}
3、tomcat中的外观模式
Tomcat源码中使用了很多的外观模式
org.apache.catalina.connector.Request
和 org.apache.catalina.connector.RequestFacade
这两个类都实现了 HttpServletRequest
接口。
在 Request
中调用 getRequest()
实际获取的是 RequestFacade
的对象
protected RequestFacade facade = null;
public HttpServletRequest getRequest() {
if (facade == null) {
facade = new RequestFacade(this);
}
return facade;
}
在 RequestFacade
中再对认为是子系统的操作进行封装
public class RequestFacade implements HttpServletRequest {
/**
* The wrapped request.
*/
protected Request request = null;
@Override
public Object getAttribute(String name) {
if (request == null) {
throw new IllegalStateException(sm.getString("requestFacade.nullRequest"));
}
return request.getAttribute(name);
}
// ...省略...
}
4、SLF4J 中的外观模式
SLF4J
是简单的日志外观模式框架,抽象了各种日志框架例如 Logback
、Log4j
、Commons-logging
和 JDK
自带的 logging
实现接口。它使得用户可以在部署时使用自己想要的日志框架。
SLF4J
没有替代任何日志框架,它仅仅是标准日志框架的外观模式。如果在类路径下除了 SLF4J
再没有任何日志框架,那么默认状态是在控制台输出日志。
日志处理框架 Logback 是 Log4j 的改进版本,原生支持SLF4J(因为是同一作者开发的),因此 Logback+SLF4J 的组合是日志框架的最佳选择,比 SLF4J+其它日志框架 的组合要快一些。而且Logback的配置可以是XML或Groovy代码。
SLF4J 的 helloworld 如下:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
logger.info("Hello World");
}
}
下图为 SLF4J 与日志处理框架的绑定调用关系
应用层调用 slf4j-api.jar
,slf4j-api.jar
再根据所绑定的日志处理框架调用不同的 jar 包进行处理。