设计模式(四)之结构型模式--适配器模式&桥接模式

[TOC]

结构型模式

作用:从程序的结构上实现松耦合,从而可以扩大整体的类结构,用来解决更大的问题

分类:

  • 适配器模式
  • 代理模式
  • 桥接模式
  • 装饰模式
  • 组合模式
  • 外观模式
  • 享元模式

一、适配器模式

适配器模式是作为两个不兼容的接口之前的桥梁,它结合了两个独立接口的功能。

以USB网线转换器为例,如下图:

​ 笔记本不能直接接网线,需要使用一个USB网线转换器(一面接网线,一面接笔记本USB接口),从而使笔记本通过网线上网

image-20210622231252326

1.1 代码实例—继承(类适配器:单继承)

1.1.1 Adaptee类

要被适配的类:网线

1
2
3
4
5
6
//要被适配的类:网线
public class Adaptee {
public void request(){
System.out.println("连接网线上网");
}
}

1.1.2 NetToUsb接口

接口转换器的抽象实现

1
2
3
4
5
//接口转换器的抽象实现
public interface NetToUsb {
//作用:处理请求,网线=>usb
public void handleRequest();
}

1.1.3 Adapter类

真正的适配器:实现NetToUsb接口

1
2
3
4
5
6
7
8
9
//真正的适配器,需要连接USB,连接网线
//extends Adaptee,使用继承,可以直接调用父类的request方法实现上网
public class Adapter extends Adaptee implements NetToUsb{
@Override
public void handleRequest() {
super.request();
}
}

1.1.4 Computer类

客户端类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//客户端类:想上网,插不上网线
public class Computer {
//我们的电脑需要连接上转接器才可以上网
public void net(NetToUsb adapter){
//上网的具体实现,找一个转接头
adapter.handleRequest();
}

public static void main(String[] args) {
//电脑,适配器,网线
Computer computer = new Computer();//电脑
Adaptee adaptee = new Adaptee();//网线
Adapter adapter = new Adapter();//转接器
computer.net(adapter);
}
}

结果:

1
连接网线上网

分析:

​ Computer类中的Adaptee adaptee = new Adaptee();好像并没有什么作用?

​ 因为Adapter类中继承了adaptee 父类,因此,adapter类自带上网功能,所以也就无需使用adaptee类。

​ 但该网线只能适用于自己电脑,并不适合其他类型的电脑,因此可以使用组合代替继承来匹配其他类型的电脑。

1.2 代码实例—对象适配器(常用)

1.2.1 Adaptee类

要被适配的类:网线

1
2
3
4
5
6
//要被适配的类:网线
public class Adaptee {
public void request(){
System.out.println("连接网线上网");
}
}

1.2.2 NetToUsb接口

1
2
3
4
5
//接口转换器的抽象实现
public interface NetToUsb {
//作用:处理请求,网线=>usb
public void handleRequest();
}

1.2.3 Adapter类

真正的适配器

区别:将之前的继承修改为组合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//真正的适配器,需要连接usb,连接网线
//组合:对象适配器,常用
public class Adapter implements NetToUsb{

private Adaptee adaptee;//使用组合方式

public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}

@Override
public void handleRequest() {
adaptee.request();//可以上网了
}
}

1.2.4 Computer类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//客户端类:想上网,插不上网线
public class Computer {

//我们的电脑需要连接上转接器才可以上网
public void net(NetToUsb adapter){
//上网的具体实现,找一个转接头
adapter.handleRequest();
}

public static void main(String[] args) {
//电脑,适配器,网线
Computer computer = new Computer();//电脑
Adaptee adaptee = new Adaptee();//网线
Adapter adapter = new Adapter(adaptee);//适配器
computer.net(adapter);
}
}

结果:

1
连接网线上网

分析:

​ 和继承相比,组合的优势在哪?

  • ​ 使用继承时,只能对某一个网线类(被继承的类)产生作用。而只用组合,可以对多个网线类都产生作用。

总结

适配器模式定义

将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作!

角色分析

  1. 目标接口:客户所期待的接口,目标可以是具体的或抽象的类,也可以是接口。对应代码中的USB接口
  2. 需要适配的类:需要适配的类或者适配者类。对应代码中的网线接口
  3. 适配器:通过包装一个需要适配的对象,把原接口转换成目标对象。对应代码中的USB转换器

图形化理解

img

优缺点

对象适配器优点:

  • 一个对象适配器可以把多个不同的适配者适配到同一目标
  • 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据里氏替换原则继承必须确保超类所拥有的性质在子类中仍然成立),适配者的子类也可以通过该适配器适配

类适配器缺点:

  • 对于Java、C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者。
  • 在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性(因此推荐使用组合方式)

适用场景

  • 系统需要使用一些现有的类,而这些类的接口(如方法名)不太符合系统的需要,甚至都没有这些类的源代码
  • 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的类,包括一些可能在将来引进的类(待了解
    • InputStreamReader(InputStream),转换流
    • SpringMVC DISPatchHandler
    • GUI 编程
    • springboot

二、桥接模式

桥接模式是将抽象部分与它的实现部分分离,使它们都可以独立地变化。又称为柄体模式或接口模式

1、简单例子说明

如下图所示:

​ 电脑分为:台式电脑、笔记本电脑、平板电脑

​ 台式电脑又分为:联想台式、苹果台式、戴尔台式等

以这种结构进行架构设计时,若想新增一个手表电脑,则在手表电脑下面需要新增三个类,特别繁琐。

且每一个类都违反了单一职责原则(一个方法尽量做一个事情),一个类负责了两个功能:品牌和类型。

image-20210624224411770

因此,可以将上述的场景进行分离,分析发现,该场景有两个变化的维度:品牌,类型。

然后将品牌和类型通过一个连接点连接起来即可。

image-20210624232818462

2、代码实例

2.1 Brand类:接口类

1
2
3
4
//品牌
public interface Brand {
void info();
}

2.2 接口实现类

接口实现类也可以在接口类中写

2.2.1 Lenovo类

1
2
3
4
5
6
7
8
//联想品牌
public class Lenovo implements Brand {

@Override
public void info() {
System.out.print("联想");
}
}

2.2.2 Apple类

1
2
3
4
5
6
7
//苹果品牌
public class Apple implements Brand {
@Override
public void info() {
System.out.print("苹果");
}
}

2.3 Computer.java

在同一个类中写,2.2小节中的代码也可以写在2.1里面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//抽象的电脑类型,可以加abstract,也可以不加
//使用类,而不是接口,是因为,品牌是电脑的一个属性,可以使用组合
public class Computer {

//组合,品牌~桥
//protected 可以让子类访问,如果设置为私有,只能类本身访问
protected Brand brand;

public Computer(Brand brand) {
this.brand = brand;
}

public void info(){
brand.info();//自带品牌
}
}

class Desktop extends Computer{
public Desktop(Brand brand) {
super(brand);
}

@Override
public void info() {
super.info();
System.out.print("台式机");
}
}

class Laptop extends Computer{
public Laptop(Brand brand) {
super(brand);
}

@Override
public void info() {
super.info();
System.out.print("笔记本");
}
}

2.4 Test类

1
2
3
4
5
6
7
8
9
10
public class Test {
public static void main(String[] args) {
//苹果笔记本
Computer computer = new Laptop(new Apple());
computer.info();
//联想台式机
computer = new Desktop(new Lenovo());
computer.info();
}
}

结果:

1
苹果笔记本联想台式机

分析:

​ 若想新增加一个品牌,则只需新增一个接口实现类,如Dell即可

​ 若想新增加一个类型,则只需新增一个类继承Computer即可。不需要修改原来的任何代码

总结

图形化理解

img

分析:

​ 从上图中,可以看出,该场景有两个维度,并且可以将每一个维度抽离出来,分为Brand和电脑

​ 其Brand(品牌)是一个接口,Lenovo、Apple都可以各自实现该接口,并且方便扩展。比如新增Dell品牌,只需实现Brand接口即可。

​ 电脑是一个类,且使用组合方式,自带Brand,且台式和笔记本都可以继承该类。在扩展时,只需要继承电脑类即可。比如,新增一个手表,也只是需要继承电脑,会自带Brand属性。

电脑和Brand如何建立连接?

  • 可以通过桥接模式,在电脑类中通过组合方式,将Brand接口组合在电脑类中,进行桥接,这样每一个电脑都会自带品牌,并且可以随意组装。

优缺点

优点:

  • 桥接模式偶尔类似于多继承方案,但是多继承方案违背了类的单一职责原则(一个方法只做一件事情),复用性比较差,类的个数也非常多。桥接模式是比多继承方案更好的解决方法,极大地减少了子类的个数,从而降低管理和维护的成本。
  • 桥接模式提高了系统的可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。符合开闭原则(对修改关闭,对扩展开放),就像一座桥,可以把两个变化的维度连接起来。

缺点:

  • 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者对抽象进行设计与编程。
  • 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。

最佳实践

  • 如果一个系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
  • 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展
  • 虽然在系统中使用继承时没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。

场景(待学习)

  1. Java语言通过Java虚拟机实现了平台的无关性。

    img

  2. AWT中的Peer架构

  3. JDBC驱动程序也是桥接模式的应用之一

0%