设计模式 - 门面模式

323

Facade,门面设计模式,也叫外观设计模式,实现起来比较好理解,记录一下它的思想和实现

一、思想

一个系统里面总是有很多接口与服务,但有一种情况是用户希望更方便地访问一连串内部接口服务,该怎么做?

  1. 在这个系统里面再写一个服务,然后这个服务集成调用了系统的其他服务
  2. 重新写一个系统,在里面引用上边的系统,在这个新系统里面写一个服务,调用用户所需的服务

评估一下:

  1. 做法 1 不推荐,因为代码冗余大,服务应该是具备复用性的,这样会使得一个系统或者说是一个类变得很臃肿
  2. 做法 2,消除了原系统的臃肿,仅仅只是单向依赖,比较合适

上面的做法 2 ,创建的一个新系统,其实就是一个门面

那么,从中也可以大概理解门面到底是什么意思了,先看看 Wiki 中比较官方的定义

A facade is an object that serves as a front-facing interface masking more complex underlying or structural code.

👉 意思就是,一个门面就是一个服务于前端的接口,这个接口可以掩盖内部系统的复杂性代码

二、实例

那么,举个实际的例子可能更好理解

食客下馆子,馆子里有很多菜品,食客想吃红烧排骨,但红烧排骨得烹饪啊,食客得一件件告诉馆子红烧排骨怎么烹饪么?

焯水();
下糖();
生抽();
老抽();
白醋();
...

肯定不能这样,食客只是想吃而已,下个命令,“我要吃红烧排骨!”,然后你给我做出来,我吃!

食客.我要吃红烧排骨();
食客.吃红烧排骨(红烧排骨);

此时,店小二登场了!告诉店小二,我要吃红烧排骨!他就等给你端上来!你吃就完事了!

至于后台,他怎么给你的订单排序,怎么通知厨师,厨师怎么做的,这些你都不用管

所以,在这个场景中,馆子就是一个提供服务的系统,里面有很多复杂的服务,例如掌柜的结账,厨师做饭,店小二是处理订单的等等

在我们上边的例子中,店小二就是这个系统的门面,因为相对于食客,他屏蔽了内部的一系列复杂的服务,只给你一个最终的结果

三、实现

回到我们在家看电影,利用电脑,也是有一系列的操作:挑电影,下载电影,播放

那么,我们也可以建立一个门面,也就是所谓的工具人,来帮我们完成这些东西,我们直接在电脑看就好了

class Computer {
    // 挑电影
    public String selectMovie() {
        return "最好看的电影";
    }
    
    // 下载电影
    public Movie downLoadMovie(String mName) {
        Movie movie = new Movie();
        movie.set(mName);
        return movie;
    }
    
    // 播放电影
    public void playing(Movie m) {
        m.playing();
    }
}

class ToolMan {
    private Computer computer = new computer();
    
    // 屏蔽操作,直接获取一系列服务的结果
    public Movie getMovie() {
        return computer.downLoadMovie(computer.selecMovie());
    }
} 

class Me {
	// 我利用工具人直接播放电影,不用自己操作那些其他操作
    ToolMan tool = new ToolMan();
    Computer.playing(tool.getMovie());
}

四、应用

4.1 SLF4J

SLF4J 不算一个新框架,它只是抽象了各种日志框架,如 Logback、Log4j、Commons-logging、JDK logging 接口,它使得用户可以在部署时使用自己想要的日志框架,所以其实它就是基于门面模式设计出来的一个整合性的框架

4.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;
	}
    
	// ...省略...
}

4.3 Tomcat

Request 对象中很多方法是内部组件之间相互交互使用的,比如 setComet()setRequestedSessionId() 等,这些不为外界调用,但必须设置为 public,因为内部还需要组件间调用,最好的方法就是通过一个 Facade 类,将内部组件之间交互使用的方法屏蔽调,只提供给外部程序感兴趣的方法,而 Tomcat 就是这么做的,这么做提高了安全性,让外部无法调用不应该调用的方法

参考