复习引入
一、几种设计模式
(1)创建型:工厂模式(简单工厂、抽象工厂)、单例模式、原型模式、建造者模式。
随着软件内部分工原来月明确,对象的创建和对象的使用分开也就成为了必然趋势。因为对象的创建会消耗掉系统的很多资源,所以单独对对象的创建进行研究,从而能够高效地创建对象就是创建型模式要探讨的问题。
(2)结构型:装饰器模式、适配器模式、组合模式、代理模式、享元模式、桥接模式、外观模式。
在解决了对象的创建问题之后,对象的组成以及对象之间的依赖关系就成了开发人员关注的焦点,因为如何设计对象的结构、继承和依赖关系会影响到后续程序的维护性、代码的健壮性、耦合性等。
(3)行为型:观察者模式、模板模式、策略模式、迭代器模式、备忘录模式、命令模式、解释器模式、中介模式、职责链模式。
在对象的结构和对象的创建问题都解决了之后,就剩下对象的行为问题了,如果对象的行为设计的好,那么对象的行为就会更清晰,它们之间的协作效率就会提高。
二、适配器模式( Adapter Design Pattern)
(1)适配器模式解决什么问题?
适配器是做接口转换,解决的是原接口和目标接口不匹配的问题。
(2)适配器模式回忆
顾名思义,这个模式就是用来做适配的,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。
EG: USB 转接头充当适配器,把两种不兼容的接口,通过转接变得可以一起工作。
实现方式:类适配器和对象适配器。其中,类适配器使用继承关系来实现,对象适配器使用组合关系来实现。
(3)适配器模式的应用场景
封装有缺陷的接口设计、统一多个类的接口设计、替换依赖的外部系统、兼容老版本接口、适配不同格式的数据
三、接口隔离原则、迪米特法则(最小知识原则)、开闭原则、依赖倒转原则
外观模式/门面模式(Facade Design Pattern)
一、为什么要提出门面模式
来解决接口的可复用性(通用性)和易用性之间的矛盾。
为了保证接口的可复用性(或者叫通用性),我们需要将接口尽量设计得细粒度一点,职责 单一一点。但是,如果接口的粒度过小,在接口的使用者开发一个业务功能时,就会导致需 要调用 n 多细粒度的接口才能完成。调用者肯定会抱怨接口不好用。相反,如果接口粒度设计得太大,一个接口返回 n 多数据,要做 n 多事情,就会导致接口不够通用、可复用性不好。接口不可复用,那针对不同的调用者的业务需求,我们就需要开发不同的接口来满足,这就会导致系统的接口无限膨胀。
粒度:粒度就是同一维度下,数据统计的粗细程度,计算机领域中粒度指系统内存扩展增量的最小值。粒度问题是设计数据仓库的一个最重要方面。粒度是指数据仓库的数据单位中保存数据的细化或综合程度的级别。细化程度越高,粒度级就越小;相反,细化程度越低,粒度级就越大。数据的粒度一直是一个设计问题。
“粗粒度和细粒度的区别主要是出于重用的目的,像类的设计:为尽可能重用,所以采用细粒度的设计模式,将一个复杂的类(粗粒度)拆分成高度重用的职责清晰的类(细粒度)。对于数据库的设计:原责:尽量减少表的数量与表与表之间的连接,能够设计成一个表的情况就不需要细分,所以可考虑使用粗粒度的设计方式。”
//一下那个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类图)
解释:假设有一个系统 A,提供了 a、b、c、d 四个接口。系统 B 完成某个业务功能,需要调用 A 系统的 a、b、d 接口。利用门面模式,我们提供一个包裹 a、b、d 接口调用的门面接口 x,给系统 B 直接使用。
代码示例:其中Facade是外观角色,也叫门面角色,客户端可以调用这个角色的方法,此角色知晓子系统的所有功能和责任,将客户端的请求代理给适当的子系统对象;Subsystem是子系统角色,可以同时拥有一个或多个子系统,每一个子系统都不是一个单独的类,而是一个类的集合,子系统并不知道门面的存在。
/**
* 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();
}
}
/**
* date: 2022/11/6
*
* @author Arc
*/
package com.qlu.test1;
public class SubSystemA {
public void dosomethingA() {
System.out.println("子系统方法A");
}
public void dosomethingAA() {
System.out.println("子系统方法AA");
}
}
/**
* date: 2022/11/6
*
* @author Arc
*/
package com.qlu.test1;
public class SubSystemB {
public void dosomethingB() {
System.out.println("子系统方法B");
}
public void dosomethingBB() {
System.out.println("子系统方法BB");
}
}
/**
* date: 2022/11/6
*
* @author Arc
*/
package com.qlu.test1;
public class SubSystemC {
public void dosomethingC() {
System.out.println("子系统方法C");
}
public void dosomethingCC() {
System.out.println("子系统方法CC");
}
}
/**
* date: 2022/11/6
*
* @author Arc
*/
package com.qlu.test1;
public class SubSystemD {
public void dosomethingD() {
System.out.println("子系统方法D");
}
public void dosomethingDD() {
System.out.println("子系统方法DD");
}
}
/**
* 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();
}
}
运行结果:
三、门面模式的实现
//基金类,基金经理人通过该类作为中间交互者,可以接受投资者的资金,进行购买和赎回操作。
/**
* 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();
}
}
//股票作为示例。内部由买入和卖出两种操作。
/**
* 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("股票1买入");
}
//卖股票
public void sell() {
System.out.println("股票1卖出");
}
}
/**
* date: 2022/11/6
*
* @author Arc
*/
package com.qlu.test2;
public class Stock3 {
//买股票
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 Client {
public static void main(String[] args) {
Fund fund = new Fund();
//基金购买
fund.buyFund();
System.out.println("-------------");
//基金赎回
fund.sellFund();
}
}
我们只需在客户端购买和赎回即可,内部的任何操作都不需要我们关注,对于面向对象有一定基础的朋友,即使没有听说过外观模式,也完全有可能在很多时候使用它,因为它完美地体现了依赖倒转原则和迪米特法则的思想,所以是非常常用的模式之一。
运行结果:
四、应用场景 ——门面模式让子系统更加易用
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
标签:void,接口,模式,门面,com,public From: https://www.cnblogs.com/purearc/p/16865459.html