有时候不想动脑子,就懒得看源码又不像浪费时间所以会看看书,但是又记不住,所以决定开始写"抄书"系列。本系列大部分内容都是来源于《 图解设计模式》(【日】结城浩 著)。该系列文章可随意转载。
Facade 模式 :简单窗口
一般来说,随着时间推移,程序会变得越来越复杂,这使得程序结构也变得越来越复杂,在使用这些功能时,我们需要先整理清楚他们之间的关系,注意正确的调用顺序。与其如此,不如为这个大型程序准备一个“窗口“,这样就无需关注每个类,只需要简单的对窗口提出请求即可。
使用 Facade 模式可以为互相关联在一起的错综复杂的类整理出高层接口。其中 Facade 角色可以让系统对外只有一个简单的接口。而且 Facade 角色还会考虑到系统内部各个类之间的责任关系和依赖关系,按照正确的顺序调用各个类。
Facade 模式 中出场的角色:
类图如下:
Demo如下:
// 基础数据获取
public class Database {
/**
* 防止外部创建
*/
private Database() {
}
/**
* 获取基础数据
* @return
*/
@SneakyThrows
public static Properties getProperties() {
Properties prop = new Properties();
prop.setProperty("123456789@qq.com", "张三");
prop.setProperty("987654321@qq.com", "李四");
return prop;
}
}
// html 工具类
public class HtmlWriter {
/**
* 生成Html内容
*
* @param title
* @throws IOException
*/
public static String createContent(String title, String userName, String email) throws IOException {
return "<html>" +
"<head>" +
"<title>" + title + "</title>" +
"</head>" +
"<body>" +
"<h1>" + title + "</h1>" +
"<div> 欢迎: " + userName + "</div>" +
"<a href=\"" + email + "\"> " + email + "</a>" +
"</html>";
}
/**
* 生成html文件
*
* @param fileName
* @param content
*/
public static void writeHtml(String fileName, String content) {
FileUtil.writeBytes(content.getBytes(StandardCharsets.UTF_8), fileName);
}
}
// 页面生成类,
public class PageMaker {
private PageMaker(){}
/**
* 构建欢迎页面
*/
public static void markWelcomePage(String mailaddr) throws IOException {
final Properties prop = Database.getProperties();
final String userName = prop.getProperty(mailaddr);
final String content = HtmlWriter.createContent(userName, userName, mailaddr);
HtmlWriter.writeHtml("D://welcome.html", content);
}
}
public class FacadeDemoMain {
public static void main(String[] args) throws IOException {
//调用 PageMaker#markWelcomePage 生成 html 页面
PageMaker.markWelcomePage("123456789@qq.com");
}
}
结果如下:生成welcome.html 文件,打开内容如图
对用户来说,只需要调用 PageMaker#markWelcomePage,传入指定邮箱便可以生成对应的 HTML页面,而其内部实现的HtmlWriter和Database对用户来说是无法感知的,即 PageMaker 对于 生成 Html 这个功能来说就是一个 Facade。
Dubbo 的分层模式个人认为也是Facade 模式的应用:Dubbo提供各个SPI 接口,如果用户需要扩展或改变功能只需要重新对应的SPI 即可。而无需关心 SPI 的上下游的关联和调用关系。如下图:
Facade 模式在项目中几乎无时无刻不在用到,这里以 Spring 中随便找的一个方法为例:DispatcherServlet#doDispatch 中 会调用 HandlerExecutionChain#applyPreHandle 方法执行拦截器方法,那么这里就可以认为 HandlerExecutionChain 对 HandlerInterceptor 进行了分装。DispatcherServlet 只需要调用HandlerExecutionChain#applyPreHandle 便可以执行拦截器方法而无需关注具体实现。
个人使用:该部分内容是写给自己看的,帮助自身理解,因此就不交代项目背景了,读者请自行忽略(???):
个人理解在微服务级别,网关也起到类似 Facade 的作用:网关对外部暴露的接口是有限且可控的,外部无需关注网关内部接口的调用关系。
在项目A 中,对同一数据的获取由于各个地区不同获取的途径或调用的第三方接口都不相同,此时需要在项目中对该数据的获取封装出一个 Facade ,当需要获取数据时只需要调用 Facade 暴露出的接口即可。Demo 如下:
public interface DataSource {
/**
* 获取数据
* @return
*/
String getData();
}
public class HttpDataSource implements DataSource {
@Override
public String getData() {
return "这是从 Http 调用获取的数据";
}
}
public class DataBaseDataSource implements DataSource {
@Override
public String getData() {
return "这是从数据库获取的数据";
}
}
public class DataFacade {
private DataBaseDataSource dataBaseDataSource = new DataBaseDataSource();
private HttpDataSource httpDataSource = new HttpDataSource();
/**
* 获取数据,nb 从数据库获取,sh通过http调用获取
* @param region
* @return
*/
public String getData(String region) {
if ("nb".equals(region)) {
return dataBaseDataSource.getData();
} else if ("sh".equals(region)) {
return httpDataSource.getData();
} else {
throw new RuntimeException("不支持");
}
}
}
public class DemoMain {
public static void main(String[] args) {
DataFacade dataFacade = new DataFacade();
System.out.println(dataFacade.getData("nb"));
System.out.println(dataFacade.getData("sh"));
}
}
输出如下:
扩展思路:
相关设计模式:
一时的小想法,仅仅个人理解,无需在意 :
Mediator 模式 : 只有一个仲裁者。
当麻烦事发生时通知仲裁者,当发生涉及全体组员的事情,也通知仲裁者,当仲裁者下达指示时,组员会立即执行。组员之间不再相互沟通作出决定,而是发生任何事都向仲裁者报,一方面仲裁者站在整个团队的角度上对组员上报的事情做出决定。最后,整个团队的交流过程就变为了组员向仲裁者报告,仲裁者向组员下达指示。
Mediator 中出场的角色:
类图如下:
Mediator 模式非常典型的应用则是在集群服务异常下线时的选举,如Redis的哨兵模式的监视和选举。
以下面为例(内容来自于书籍 《Redis设计与实现》):
3. 当 server1 的下线时长超过用户设定的下线时长上限时,Sentinel 系统就会对 server1 执行故障转移操作:
个人使用:该部分内容是写给自己看的,帮助自身理解,因此就不交代项目背景了,读者请自行忽略(???):
Mediator 模式在目前的经验中似乎并没有用到,不过临时想到了一个应用场景:类如支付宝登录这种需要极度安全的操作肯定不能直接通过用户名密码验证,需要根据当前使用的手机是否新手机、当前账号多久没有登录、截止目前登录了多少次等种种因素决定当前登录是否有效,如此便可以使用仲裁者模式。Demo如下:
// 判断接口
public interface Colleague {
/**
* 信息判决
* @param userInfo
* @return
*/
int judgment(String userInfo);
}
public class LoginMadiator {
// 登录次数验证
private NumberColleague numberColleague = new NumberColleague();
// 手机号验证
private PhoneColleague phoneColleague = new PhoneColleague();
/**
* 登录判决
*
* @return
*/
public boolean loginJudgment(String userInfo) {
// 根据用户信息获取登录次数的判定
final int numberScore = numberColleague.judgment(userInfo);
// 根据用户信息获取是否新手机的的判定
final int phoneScore = phoneColleague.judgment(userInfo);
// 如果登录次数或是否新手机的判定有一个小于80分,本次登录验证不通过。
return numberScore < 80 || phoneScore < 80;
}
}
public class DemoMain {
public static void main(String[] args) {
String userName = "夏义春";
String password = "123456789";
LoginService loginService = new LoginService ();
// 用户登录, 返回用户登录信息
final String userInfo = loginService.login(userName, password);
LoginMadiator loginMadiator = new LoginMadiator();
// 如果本次登录裁决失败,则进行人脸或短信验证
if (!loginMadiator.loginJudgment(userInfo)){
// TODO : 进行人脸验证 或短信验证
}
}
}
相关的设计模式:
一时的小想法,仅仅个人理解,无需在意 :