# 外观模式 (Facade)
外观模式 (Facade) - 简书
# 总结
外观模式就是为子系统中的一组接口提供一个一致的界面,Facade 模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
就类似于我们后端所写的接口,然后前端只需要传参数调接口就行,他们根本不知道接口里面到底干了什么,也不需要知道干了什么。
这里先对两个词进行一下说明,一个是界面,一个是接口。
一提到界面,估计很多朋友的第一反应就是图形界面(GUI)。其实在这里提到的界面,主要指的是从一个组件外部来看这个组件,能够看到什么,这就是这个组件的界面,也就是所说的外观。
比如:你从一个类外部来看这个类,那么这个类的 public 方法就是这个类的外观,因为你从类外部来看这个类,就能看到这些。
再比如:你从一个模块外部来看这个模块,那么这个模块对外的接口就是这个模块的外观,因为你就只能看到这些接口,其它的模块内部实现的东西是被接口封装隔离了的。
一提到接口,做 Java 的朋友的第一反应就是 interface。其实在这里提到的接口,主要是指的外部和内部交互的这么一个通道,通常是指的一些方法,可以是类的方法,也可以是 interface 的方法。也就是说,这里说的接口,并不等价于 interface,也有可能是一个类。
应用外观模式来解决的思路
仔细分析上面的问题,客户端想要操作更简单点,那就根据客户端的需要来给客户端定义一个简单的接口,然后让客户端调用这个接口,剩下的事情就不用客户端管,这样客户端就变得简单了。
当然,这里所说的接口就是客户端和被访问的系统之间的一个通道,并不一定是指 Java 的 interface。事实上,这里所说的接口,在外观模式里面,通常指的是类,这个类被称为 “外观”。
外观模式就是通过引入这么一个外观类,在这个类里面定义客户端想要的简单的方法,然后在这些方法的实现里面,由外观类再去分别调用内部的多个模块来实现功能,从而让客户端变得简单,这样一来,客户端就只需要和外观类交互就可以了。
# 结构
Facade:定义子系统的多个模块对外的高层接口,通常需要调用内部多个模块,从而把客户的请求代理给适当的子系统对象。
模块:接受 Facade 对象的委派,真正实现功能,各个模块之间可能有交互。但是请注意,Facade 对象知道各个模块,但是各个模块不应该知道 Facade 对象。
# 本质
外观模式的本质:封装交互,简化调用。
Facade 封装了子系统外部和子系统内多个模块的交互过程,从而简化外部的调用。通过外观,子系统为外部提供一些高层的接口,以方便它们的使用。
# 适配器 (Adapter)
参考:
- 图解设计模式
- 【结构型模式八】适配器 (Adapter) - 简书 (jianshu.com)
如图,中间的转换器可以当成适配器
# 背景
如果想让额定工作电压是直流 12 伏特的笔记本电脑在交流 100 伏特” 的 AC 电源下工作,应该怎么做呢?通常,我们会使用 AC 适配器,将家庭用的交流 100 伏特电压转换成我们所需要的直流 12 伏特电压。这就是适配器的工作,它位于实际情况与需求之间,填补两者之间的差异。适配器的英文是 Adapter, 意思是 “使… 相互适合的东西”。前面说的 AC 适配器的作用就是让工作于直流 12 伏特环境的笔记本电脑适合于交流 100 伏特的环境。
# 总结
将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适配器也可以是调用已经完成的接口,来实现新的接口。
要使用适配器模式来实现示例,关键就是要实现这个适配器对象,它需要实现第二版的接口,但是在内部实现的时候,需要调用第一版已经实现的功能。也就是说,第二版的接口就相当于适配器模式中的 Target 接口,而第一版已有的实现就相当于适配器模式中的 Adaptee 对象。
# 结构
Client:客户端,调用自己需要的领域接口 Target。
Target:定义客户端需要的跟特定领域相关的接口。
Adaptee:已经存在的接口,通常能满足客户端的功能要求,但是接口与客户端要求的特定领域接口不一致,需要被适配。
Adapter:适配器,把 Adaptee 适配成为 Client 需要的 Target。
# 本质
适配器模式的本质:转换匹配,复用功能。
适配器通过转换调用已有的实现,从而能把已有的实现匹配成需要的接口,使之能满足客户端的需要。也就是说转换匹配是手段,而复用已有的功能才是目的。
在进行转换匹配的过程中,适配器还可以在转换调用的前后实现一些功能处理,也就是实现智能的适配。
# 实例
实例背景如图,已有功能 Banner类
,需求 Print接口
,根据 Banner类
完成 Print接口
的需求。
# 使用继承的适配器
现在的情况
public class Banner { | |
private String string; | |
public Banner (String string){ | |
this.string = string; | |
} | |
public void showWithParen(){ | |
System.out.println("("+string+")"); | |
} | |
public void showWithAster(){ | |
System.out.println("*"+string+"*"); | |
} | |
} |
需求接口
public interface Print { | |
void printWeak(); | |
void printStrong(); | |
} |
PrintBanner 扮演适配器角色
public class PrintBanner extends Banner implements Print { | |
public PrintBanner(String string) { | |
super(string); | |
} | |
@Override | |
public void printWeak() { | |
showWithParen(); | |
} | |
@Override | |
public void printStrong() { | |
showWithAster(); | |
} | |
} |
客户端
public class Main { | |
public static void main(String[] args) { | |
PrintBanner printBanner = new PrintBanner("xttani"); | |
printBanner.showWithAster(); | |
printBanner.showWithParen(); | |
} | |
} |
# 什么时候使用
如果你想要使用一个已经存在的类,但是它的接口不符合你的需求,这种情况可以使用适配器模式,来把已有的实现转换成你需要的接口
如果你想创建一个可以复用的类,这个类可能和一些不兼容的类一起工作,这种情况可以使用适配器模式,到时候需要什么就适配什么
如果你想使用一些已经存在的子类,但是不可能对每一个子类都进行适配,这种情况可以选用对象适配器,直接适配这些子类的父类就可以了。
版本升级和兼容性问题也可以使用适配器
# 桥接 (Bridge)
参考:
- 图解设计模式
- 【结构型模式九】桥接模式 (Bridge) - 简书 (jianshu.com)
# 总结
定义:将抽象部分与它的实现部分分离,使它们都可以独立地变化。
当我们想要编写子类时,就需要像这样先确认自己的意图:“我是要增加功能呢?还是要增加实现呢?” 当类的层次结构只有一层时,功能层次结构与实现层次结构是混杂在一个层次结构中的。这样很容易使类的层次结构变得复杂,也难以透彻地理解类的层次结构。因为自己难以确定究竟应该在类的哪一个层次结构中去增加子类。
因此,我们需要将 “类的功能层次结构” 与 “类的实现层次结构” 分离为两个独立的类层次结构。
当然,如果只是简单地将它们分开,两者之间必然会缺少联系。所以我们还需要在它们之间搭建一座桥梁。本章中要学习的 Bridge 模式的作用就是搭建这座桥梁
# 背景
Bridge 的意思是 “桥梁”。就像在现实世界中,桥梁的功能是将河流的两侧连接起来一样,
Bidg 模式的作用也是将两样东西连接起来,它们分别是类的功能层次结构和类的实现层次结构。
Bridge 模式的作用是在 “类的功能层次结构” 和 “类的实现层次结构” 之间搭建桥梁
# 结构
**Abstraction:抽象部分的接口。** 通常在这个对象里面,要维护一个实现部分的对象引用,在抽象对象里面的方法,需要调用实现部分的对象来完成。这个对象里面的方法,通常都是跟具体的业务相关的方法。
**RefinedAbstraction:扩展抽象部分的接口,** 通常在这些对象里面,定义跟实际业务相关的方法,这些方法的实现通常会使用 Abstraction 中定义的方法,也可能需要调用实现部分的对象来完成。
**Implementor:定义实现部分的接口,** 这个接口不用和 Abstraction 里面的方法一致,通常是由 Implementor 接口提供基本的操作,而 Abstraction 里面定义的是基于这些基本操作的业务方法,也就是说 Abstraction 定义了基于这些基本操作的较高层次的操作。
ConcreteImplementor:真正实现 Implementor 接口的对象。
# 本质
桥接模式的本质:分离抽象和实现。
** 桥接模式最重要的工作就是分离抽象部分和实现部分,这是解决问题的关键。** 只有把抽象和实现分离开了,才能够让它们可以独立的变化;只有抽象和实现可以独立的变化,系统才会有更好的可扩展性、可维护性。
至于其它的好处,比如:可以动态切换实现、可以减少子类个数等。都是把抽象部分和实现部分分离过后,带来的,如果不把抽象部分和实现部分分离开,那就一切免谈了。所以综合来说,桥接模式的本质在于 “分离抽象和实现”。
# 实例
public class Display {
private DisplayImpl impl;
public Display(DisplayImpl impl) {
this.impl = impl;
}
public void open() {
impl.rawOpen();
}
public void print() {
impl.rawPrint();
}
public void cloese() {
impl.rawClose();
}
public final void display() {
open();
print();
cloese();
}
}
public class CountDisplay extends Display{ | |
public CountDisplay(DisplayImpl impl) { | |
super(impl); | |
} | |
public void multiDisplay(int times){ | |
open(); | |
for (int i = 0; i < times; i++) { | |
print(); | |
} | |
cloese(); | |
} | |
} |
public abstract class DisplayImpl { | |
public abstract void rawOpen(); | |
public abstract void rawPrint(); | |
public abstract void rawClose(); | |
} |
public class StringDisplayImpl extends DisplayImpl { | |
private String string; | |
public int width; | |
public StringDisplayImpl(String s) { | |
this.string = s; | |
this.width = s.getBytes(StandardCharsets.UTF_8).length; | |
} | |
@Override | |
public void rawOpen() { | |
printLine(); | |
} | |
@Override | |
public void rawPrint() { | |
System.out.println("|" + string + "|"); | |
} | |
@Override | |
public void rawClose() { | |
printLine(); | |
} | |
private void printLine() { | |
System.out.print("+"); | |
for (int i = 0; i < width; i++) { | |
System.out.print("-"); | |
} | |
System.out.println("+"); | |
} | |
} |
Bridge 模式的特征是将 “类的功能层次结构” 与 “类的实现层次结构” 分离开了。将类的这两个层次结构分离开有利于独立地对它们进行扩展。
当想要增加功能时,只需要在 “类的功能层次结构” 一侧增加类即可,不必对 “类的实现层次结构” 做任何修改。而且,增加后的功能可以被 “所有的实现” 使用。