门面模式

复习引入

一、几种设计模式

(1)创建型:工厂模式(简单工厂、抽象工厂)、单例模式、原型模式、建造者模式。

​ 随着软件内部分工原来月明确,对象的创建和对象的使用分开也就成为了必然趋势。因为对象的创建会消耗掉系统的很多资源,所以单独对对象的创建进行研究,从而能够高效地创建对象就是创建型模式要探讨的问题。

(2)结构型:装饰器模式、适配器模式、组合模式、代理模式、享元模式、桥接模式、外观模式。

​ 在解决了对象的创建问题之后,对象的组成以及对象之间的依赖关系就成了开发人员关注的焦点,因为如何设计对象的结构、继承和依赖关系会影响到后续程序的维护性、代码的健壮性、耦合性等。

(3)行为型:观察者模式、模板模式、策略模式、迭代器模式、备忘录模式、命令模式、解释器模式、中介模式、职责链模式。

​ 在对象的结构和对象的创建问题都解决了之后,就剩下对象的行为问题了,如果对象的行为设计的好,那么对象的行为就会更清晰,它们之间的协作效率就会提高。

二、适配器模式( Adapter Design Pattern)

(1)适配器模式解决什么问题?

​ 适配器是做接口转换,解决的是原接口和目标接口不匹配的问题。

(2)适配器模式回忆

​ 顾名思义,这个模式就是用来做适配的,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。

​ EG: USB 转接头充当适配器,把两种不兼容的接口,通过转接变得可以一起工作。

​ 实现方式:类适配器和对象适配器。其中,类适配器使用继承关系来实现,对象适配器使用组合关系来实现。

(3)适配器模式的应用场景

​ 封装有缺陷的接口设计、统一多个类的接口设计、替换依赖的外部系统、兼容老版本接口、适配不同格式的数据

三、接口隔离原则、迪米特法则(最小知识原则)、开闭原则、依赖倒转原则

外观模式/门面模式(Facade Design Pattern)

一、为什么要提出门面模式

​ 来解决接口的可复用性(通用性)和易用性之间的矛盾。

​ 为了保证接口的可复用性(或者叫通用性),我们需要将接口尽量设计得细粒度一点,职责 单一一点。但是,如果接口的粒度过小,在接口的使用者开发一个业务功能时,就会导致需 要调用 n 多细粒度的接口才能完成。调用者肯定会抱怨接口不好用。相反,如果接口粒度设计得太大,一个接口返回 n 多数据,要做 n 多事情,就会导致接口不够通用、可复用性不好。接口不可复用,那针对不同的调用者的业务需求,我们就需要开发不同的接口来满足,这就会导致系统的接口无限膨胀。

​ 粒度:粒度就是同一维度下,数据统计的粗细程度,计算机领域中粒度指系统内存扩展增量的最小值。粒度问题是设计数据仓库的一个最重要方面。粒度是指数据仓库的数据单位中保存数据的细化或综合程度的级别。细化程度越高,粒度级就越小;相反,细化程度越低,粒度级就越大。数据的粒度一直是一个设计问题。

​ “粗粒度和细粒度的区别主要是出于重用的目的,像类的设计:为尽可能重用,所以采用细粒度的设计模式,将一个复杂的类(粗粒度)拆分成高度重用的职责清晰的类(细粒度)。对于数据库的设计:原责:尽量减少表的数量与表与表之间的连接,能够设计成一个表的情况就不需要细分,所以可考虑使用粗粒度的设计方式。”

copy
//一下那个TaskService是粗粒度的,哪个是细粒度的? /** * date: 2022/11/6 * * @author Arc */ package com.qlu.test1; interface TaskService{ public List getTaskById(int id); public List getTaskByName(String name); public List getTaskByAge(int age); } /** * date: 2022/11/6 * * @author Arc */ package com.qlu.test1; interface TaskService{ public List getTask(Person person); } /** * date: 2022/11/6 * * @author Arc */ package com.qlu.test1; @Data public class Person { private String name; private String gender; private int age; }

二、门面模式的定义

​ GoF中的《设计模式》中描述:Provide a unified interface to a set of interfaces in a subsystem. Facade Pattern defines a higher-level interface that makes the subsystem easier to use. 翻译成中文就是:门面模式为子系统提供一组统一的接口,定义一组高层接口让子系统更易用。(下图为门面模式的UML类图)

image-20221106110659894

​ 解释:假设有一个系统 A,提供了 a、b、c、d 四个接口。系统 B 完成某个业务功能,需要调用 A 系统的 a、b、d 接口。利用门面模式,我们提供一个包裹 a、b、d 接口调用的门面接口 x,给系统 B 直接使用。

​ 代码示例:其中Facade是外观角色,也叫门面角色,客户端可以调用这个角色的方法,此角色知晓子系统的所有功能和责任,将客户端的请求代理给适当的子系统对象;Subsystem是子系统角色,可以同时拥有一个或多个子系统,每一个子系统都不是一个单独的类,而是一个类的集合,子系统并不知道门面的存在。

copy
/** * date: 2022/11/6 * * @author Arc */ package com.qlu.test1; public class Facade { //被委托的对象 SubSystemA a; SubSystemB b; SubSystemC c; SubSystemD d; public Facade() { a = new SubSystemA(); b = new SubSystemB(); c = new SubSystemC(); d = new SubSystemD(); } //提供给外部访问的方法 public void methodA() { this.a.dosomethingA(); } public void methodB() { this.b.dosomethingB(); } public void methodC() { this.c.dosomethingC(); } public void methodD() { this.d.dosomethingD(); } }

copy
/** * date: 2022/11/6 * * @author Arc */ package com.qlu.test1; public class SubSystemA { public void dosomethingA() { System.out.println("子系统方法A"); } } /** * date: 2022/11/6 * * @author Arc */ package com.qlu.test1; public class SubSystemB { public void dosomethingB() { System.out.println("子系统方法B"); } } /** * date: 2022/11/6 * * @author Arc */ package com.qlu.test1; public class SubSystemC { public void dosomethingC() { System.out.println("子系统方法C"); } } /** * date: 2022/11/6 * * @author Arc */ package com.qlu.test1; public class SubSystemD { public void dosomethingD() { System.out.println("子系统方法D"); } }
copy
/** * date: 2022/11/6 * * @author Arc */ package com.qlu.test1; public class Client { public static void main(String[] args) { Facade facade = new Facade(); facade.methodA(); facade.methodB(); } }

运行结果:

image-20221106140454145

三、门面模式的实现

copy
//基金类,基金经理人通过该类作为中间交互者,可以接受投资者的资金,进行购买和赎回操作。 /** * date: 2022/11/6 * * @author Arc */ package com.qlu.test2; public class Fund { Stock1 stock1; Stock2 stock2; Stock3 stock3; public Fund() { stock1 = new Stock1(); stock2 = new Stock2(); stock3 = new Stock3(); } //购买基金 public void buyFund() { stock1.buy(); stock2.buy(); stock3.buy(); } //赎回基金 public void sellFund() { stock1.sell(); stock2.sell(); stock3.sell(); } }

copy
//股票作为示例。内部由买入和卖出两种操作。 /** * date: 2022/11/6 * * @author Arc */ package com.qlu.test2; public class Stock1 { //买股票 public void buy() { System.out.println("股票1买入"); } //卖股票 public void sell() { System.out.println("股票1卖出"); } } /** * date: 2022/11/6 * * @author Arc */ package com.qlu.test2; public class Stock2 { //买股票 public void buy() { System.out.println("股票2买入"); } //卖股票 public void sell() { System.out.println("股票2卖出"); } } /** * date: 2022/11/6 * * @author Arc */ package com.qlu.test2; public class Stock3 { //买股票 public void buy() { System.out.println("股票3买入"); } //卖股票 public void sell() { System.out.println("股票3卖出"); } }
copy
//用户通过该类对基金进行购买和赎回操作。 /** * date: 2022/11/6 * * @author Arc */ package com.qlu.test2; public class Client { public static void main(String[] args) { Fund fund = new Fund(); //基金购买 fund.buyFund(); System.out.println("-------------"); //基金赎回 fund.sellFund(); } }

​ 我们只需在客户端购买和赎回即可,内部的任何操作都不需要我们关注,对于面向对象有一定基础的朋友,即使没有听说过外观模式,也完全有可能在很多时候使用它,因为它完美地体现了依赖倒转原则和迪米特法则的思想,所以是非常常用的模式之一。

运行结果:

image-20221107180959777

四、应用场景 ——门面模式让子系统更加易用

1、解决易用性问题

门面模式可以用来封装系统的底层实现,隐藏系统的复杂性,提供一组更加简单易用、更高层的接口。比如,Linux 系统调用函数就可以看作一种“门面”。它是 Linux 操作系统暴露给开发者的一组“特殊”的编程接口,它封装了底层更基础的 Linux 内核调用。再比如, Linux 的 Shell 命令,实际上也可以看作一种门面模式的应用。它继续封装系统调用,提供 更加友好、简单的命令,让我们可以直接通过执行命令来跟操作系统交互。

接口隔离原则的英文翻译是“ Interface Segregation Principle”,缩写为 ISP。Robert Martin 在 SOLID 原则中是这样定义它的:“Clients should not be forced to depend upon interfaces that they do not use。”直译成中文的话就是:客户端不应该强迫依赖它不需要的接口。

2、解决性能问题

​ 通过将多个接口调用替换为一个门面接口调用,减少网络通信成本,提高 App 客户端的响应速度。

​ EG:系统 A 是一个后端服务器,系统 B 是 App 客户端。App 客户端通过后端服务器提供的接口来获取数据。我们知道,App 和服务器之间是通过移动网络通信 的,网络通信耗时比较多,为了提高 App 的响应速度,我们要尽量减少 App 与服务器之 间的网络通信次数。 假设,完成某个业务功能(比如显示某个页面信息)需要“依次”调用 a、b、d 三个接 口,因自身业务的特点,不支持并发调用这三个接口。 如果我们现在发现 App 客户端的响应速度比较慢,排查之后发现,是因为过多的接口调用 过多的网络通信。针对这种情况,我们就可以利用门面模式,让后端服务器提供一个包裹 a、b、d 三个接口调用的接口 x。App 客户端调用一次接口 x,来获取到所有想要的数 据,将网络通信的次数从 3 次减少到 1 次,也就提高了 App 的响应速度。

​ 从代码实现的角度来看,该如何组织门面接口和非门面接口? (可以过,因为我没有开发经验)

​ A:如果门面接口不多,我们完全可以将它跟非门面接口放到一块,也不需要特殊标记,当作普通接口来用即可。如果门面接口很多,我们可以在已有的接口之上,再重新抽象出一层,专门放置门面接口,从类、包的命名上跟原来的接口层做区分。如果门面接口特别多,并且很多都是跨多个子系统的,我们可以将门面接口放到一个新的子系统中。

3、 解决分布式事务问题

​ 关于利用门面模式来解决分布式事务问题,我们通过一个例子来解释一下。

在一个金融系统中,有两个业务领域模型,用户和钱包。这两个业务领域模型都对外暴露了 一系列接口,比如用户的增删改查接口、钱包的增删改查接口。假设有这样一个业务场景: 在用户注册的时候,我们不仅会创建用户(在数据库 User 表中),还会给用户创建一个钱包(在数据库的 Wallet 表中)。 对于这样一个简单的业务需求,我们可以通过依次调用用户的创建接口和钱包的创建接口来 完成。但是,用户注册需要支持事务,也就是说,创建用户和钱包的两个操作,要么都成 功,要么都失败,不能一个成功、一个失败。 要支持两个接口调用在一个事务中执行,是比较难实现的,这涉及分布式事务问题。虽然我 们可以通过引入分布式事务框架或者事后补偿的机制来解决,但代码实现都比较复杂。而最 简单的解决方案是,利用数据库事务或者 Spring 框架提供的事务(如果是 Java 语言的 话),在一个事务中,执行创建用户和创建钱包这两个 SQL 操作。这就要求两个 SQL 操作 要在一个接口中完成,所以,我们可以借鉴门面模式的思想,再设计一个包裹这两个操作的 新接口,让新接口在一个事务中执行两个 SQL 操作。

课上思考

一、适配器模式和门面模式的共同点是,将不好用的接口适配成好用的接口。你可以试着总 结一下它们的共同点和区别吗?

​ A1:适配器是做接口转换,解决的是原接口和目标接口不匹配的问题。 门面模式做接口整合,解决的是多接口调用带来的问题。适配器模式与门面模式的共同点都需要二次封装,隐藏内部细节。不同点为适配器是为 了统一格式,门面是为了简单易用。

二、门面模式的优缺点

​ 优点:减少了系统的相互依赖;提高灵活性,不管系统内部如何变化,只要不影响外观对象,可以自由活动;提高了安全性,想让你访问那些业务就开通哪些逻辑,不在外观上开通的方法就访问不到。

​ 缺点:不符合开闭原则,修改麻烦。

三、外观模式体现了哪些设计原则

​ 迪米特法则、接口隔离原则、依赖倒转原则

参考: https://www.cnblogs.com/ajunForNet/p/3492101.html

https://www.cnblogs.com/adamjwh/p/9048594.html

https://www.cnblogs.com/HoneyTYX/p/9396446.html

https://baike.baidu.com/item/粒度/13014724?fr=aladdin

posted @   Purearc  阅读(117)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
🚀