浅谈设计模式之结构型模式
前言
通过学习设计模式,我们知道根据目的、用途的不同,把设计模式分为创建型模式、结构型模式、行为型模式。
- 创建型模式主要用于创建对象;
- 结构型模式主要用于处理类和对象的组合;
- 行为型模式主要用于描述类或对象的交互以及职责分配
本篇,我想对结构型模式进行一番总结、探讨。
认识结构型模式
结构型模式所描述的是如何将类和对象结合在一起来形成一个更大的结构,它描述两种不同的事物:类和对象,根据这一点,可分为类结构型和对象结构型模式。类结构型模式关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系;对象结构型模式关心类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法。根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系,因此大部分结构型模式都是对象结构型模式
结构型模式的实例
- 适配器模式:将一个类的接口转换成客户希望的另外一种接口,这样就能实现已有接口的复用。适配器主要有类适配器和对象适配器两种实现方式,通常情况下,推荐优先使用对象适配器方式。
- 桥接模式:将抽象部分与实现部分分离,使它们都可以独立地变化。它主要用于应对多维度变化点问题,通过对象组合的方式,可以极大地减少子类的数目,同时还能让不同维度独立扩展变化。
- 组合模式:将对象组合成树形结构以表示“整合-部分”的层次结构,从而使得用户对单个对象和组合对象的使用具有一致性,也就是客户端能够透明地无区别地操作两者。
- 装饰模式:动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式相比生成子类更为灵活。假若使用多继承的方式来完成职责的添加,将会不可避免地造成子类数目的“爆炸性”增长,此外,因为是静态增加的,那也就不可能在运行状态时动态地添加或者删除额外职责呢。
- 外观模式:为子系统中的一组接口提供一个一致的接口,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。这样原来需要客户直接与复杂的子系统打交道、交互,现在这一过程将完全将交由外观对象来完成,极大地方便了客户端的调用。
- 享元模式:运用共享技术有效地支持大量细粒度的对象。享元模式关键是将对象的内部状态和外部状态分离,尽可能地对“稳定”的内部状态进行共享,而将会随运用场景而改变的状态通过外部状态传入。
- 代理模式:为其他对象提供一种代理以控制对这个对象的访问。主要是在客户端和目标对象间增加一层间接层,通过这个间接层来完成对目标对象的种种控制操作,所以也就形成了不同功能类型的代理呢,比如远程代理、保护代理和虚代理等等。
- ···
以适配器模式为例,代码解析
说到适配器,我们最熟悉的莫过于电源适配器了,也就是手机的充电头。它就是适配器模式的一个应用。
大家可以试想一下,如果你有一条连接电脑和手机的 USB 数据线,连接电脑的一端从电脑接口处接收 5V 的电压,连接手机的一端向手机输出 5V 的电压,并且它们都工作良好。
我们常用的家用电压都是 220V,所以 USB 数据线不能直接拿来给手机充电,这时候我们有两种方案:
- 一、单独制作手机充电器,接收 220V 家用电压,输出 5V 电压。
- 二、添加一个适配器,将 220V 家庭电压转化为类似电脑接口的 5V 电压,再连接数据线给手机充电。
如果你使用过早期的手机,就会知道以前的手机厂商采用的就是第一种方案:早期的手机充电器都是单独制作的,充电头和充电线是连在一起的,但现在的手机都采用了电源适配器加数据线的方案。
现在我要说的适配器模式,就是将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
适配的意思是适应、匹配。通俗地讲,适配器模式适用于有相关性但不兼容的结构,源接口通过一个中间件转换后才可以适用于目标接口,这个转换过程就是适配,这个中间件就称之为适配器。
家用电源和 USB 数据线有相关性:家用电源输出电压,USB 数据线输入电压。但两个接口无法兼容,因为一个输出 220V,一个输入 5V,通过适配器将输出 220V 转换成输出 5V 之后才可以一起工作。
接下来,我用程序模拟一下这个过程:
家庭电源提供220V的电压
HomeBattery类:
代码语言:javascript复制class HomeBattery {
int supply() {
// 家用电源提供一个 220V 的输出电压
return 220;
}
}
复制代码
USB 数据线只接收 5V 的充电电压
USBLine类:
代码语言:javascript复制class USBLine {
void charge(int volt) {
// 如果电压不是 5V,抛出异常
if (volt != 5) throw new IllegalArgumentException("只能接收 5V 电压");
// 如果电压是 5V,正常充电
System.out.println("正常充电");
}
}
复制代码
先来看看适配之前,用户如果直接用家庭电源给手机充电:
User类
代码语言:javascript复制public class User {
@Test
public void chargeForPhone() {
HomeBattery homeBattery = new HomeBattery();
int homeVolt = homeBattery.supply();
System.out.println("家庭电源提供的电压是 " homeVolt "V");
USBLine usbLine = new USBLine();
usbLine.charge(homeVolt);
}
}
复制代码
运行结果如下:
家庭电源提供的电压是 220V
java.lang.IllegalArgumentException: 只能接收 5V 电压
这时,如果加入电源适配器:
Adapter类
代码语言:javascript复制class Adapter {
int convert(int homeVolt) {
// 适配过程:使用电阻、电容等器件将其降低为输出 5V
int chargeVolt = homeVolt - 215;
return chargeVolt;
}
}
复制代码
然后,用户再使用适配器将家庭电源提供的电压转换为充电电压:
User类
代码语言:javascript复制public class User {
@Test
public void chargeForPhone() {
HomeBattery homeBattery = new HomeBattery();
int homeVolt = homeBattery.supply();
System.out.println("家庭电源提供的电压是 " homeVolt "V");
Adapter adapter = new Adapter();
int chargeVolt = adapter.convert(homeVolt);
System.out.println("使用适配器将家庭电压转换成了 " chargeVolt "V");
USBLine usbLine = new USBLine();
usbLine.charge(chargeVolt);
}
}
复制代码
运行结果如下:
家庭电源提供的电压是 220V
使用适配器将家庭电压转换成了 5V
正常充电
这就是适配器模式。在我们日常的开发中经常会使用到各种各样的 Adapter,都属于适配器模式的应用。
但适配器模式并不推荐多用。因为未雨绸缪好过亡羊补牢,如果事先能预防接口不同的问题,不匹配问题就不会发生,只有遇到源接口无法改变时,才应该考虑使用适配器。比如现代的电源插口中很多已经增加了专门的充电接口,让我们不需要再使用适配器转换接口,这又是社会的一个进步。
总结
以上内容是我对结构型模式做的一次简单的总结,让大家对结构型模式整体上有一些理解和认识,文中我以结构型模式中的适配器模式为例,进行了代码演示,也能让大家进一步进行了解结构型模式,程序是一个不断改进的过程,希望我们学了设计模式之后能够学以致用,优化自己的程序。