结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。
由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。
结构型模式分为以下 7 种:
- 代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
- 适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
- 桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现的,从而降低了抽象和实现这两个可变维度的耦合度。
- 装饰(Decorator)模式:动态地给对象增加一些职责,即增加其额外的功能。
- 外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
- 享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。
- 组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
以上 7 种结构型模式,除了适配器模式分为类结构型模式和对象结构型模式两种,其他的全部属于对象结构型模式。
(图片来源:https://www.jianshu.com/p/8a293e4a888e)
定义
将一个与当前类无法兼容的接口转换成能够兼容当前类的接口。
现在有一个第三方类/我们项目组中别人写的类放在我们面前,我们需要调用其中的功能,但是第三方类提供给我们的接口和我们程序的接口不相匹配,这时我们就需要再写一个适配器类,把第三方类不匹配的接口转换成能被我们程序调用的接口。
优点:
- 客户端通过适配器可以透明地调用目标接口。
- 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。
- 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
缺点:对类适配器来说,更换适配器的实现过程比较复杂。
模式结构:
- 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
- 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
- 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
举例说明
适配器模式有三种实现方式,下面都以基金交易平台的例子来解释。
- 基金公司的交易接口:
/**
* 基金公司的交易接口
*/
class FundCompanyTrade{
/**
* 买入函数
*/
public void mairu() {
// ……
}
/**
* 卖出函数
*/
public void maichu() {
// ……
}
}
- 基金交易平台的交易接口
/**
* 基金交易平台的交易接口
*/
interface FundPlatformTrade {
// 买入接口
void buy();
// 卖出接口
void sell();
}
- 基金交易平台均通过如下代码调用各个基金公司的交易接口:
class Client {
@Autowired
private FundPlatformTrade fundPlatformTrade;
/**
* 买入基金
*/
public void buy() {
fundPlatformTrade.buy();
}
/**
* 卖出基金
*/
public void sell() {
fundPlatformTrade.sell();
}
}
方式1:类适配器
通过继承来实现接口的转换。
- 基金交易适配器:
class Adapter extends FundCompanyTrade implements FundPlatformTrade {
void buy() {
mairu();
}
void sell(){
maichu();
}
}
适配器Adapter继承了FundCompanyTrade,因此拥有了FundCompanyTrade买入和卖出的能力;适配器Adapter又实现了FundPlatformTrade,因此需要实现其中的买入和卖出接口,这个过程便完成了基金公司交易接口向基金平台交易接口的转换。
使用时,只需将Adapter通过Spring注入给Client类的fundPlatformTrade成员变量即可:
<!-- 声明Adapter对象 -->
<bean name="adapter" class="Adapter">
</bean>
<!-- 将adapter注入给Client -->
<bean class="Client">
<property name="fundPlatformTrade" ref="adapter" />
</bean>
方式2:对象适配器
通过组合来实现接口的转换。
- 基金交易适配器:
class Adapter implements FundPlatformTrade {
@Autowired
private FundCompanyTrade fundCompanyTrade;
void buy() {
fundCompanyTrade.mairu();
}
void sell(){
fundCompanyTrade.maichu();
}
}
这种方式中,适配器Adapter
并未继承FundCompanyTrade
,而是将该对象作为成员变量注入进来,一样可以达到同样的效果。
方式3:接口适配器
当存在这样一个接口,其中定义了N多的方法,而我们现在却只想使用其中的一个到几个方法,如果我们直接实现接口,那么我们要对所有的方法进行实现,哪怕我们仅仅是对不需要的方法进行置空(只写一对大括号,不做具体方法实现)也会导致这个类变得臃肿,调用也不方便,这时我们可以使用一个抽象类作为中间件,即适配器,用这个抽象类实现接口,而在抽象类中所有的方法都进行置空,那么我们在创建抽象类的继承类,而且重写我们需要使用的那几个方法即可。
- 目标接口:A
public interface A {
void a();
void b();
void c();
void d();
void e();
void f();
}
- 适配器:Adapter
实现所有函数,将所有函数先置空
public abstract class Adapter implements A {
public void a(){
throw new UnsupportedOperationException("对象不支持这个功能");
}
public void b(){
throw new UnsupportedOperationException("对象不支持这个功能");
}
public void c(){
throw new UnsupportedOperationException("对象不支持这个功能");
}
public void d(){
throw new UnsupportedOperationException("对象不支持这个功能");
}
public void e(){
throw new UnsupportedOperationException("对象不支持这个功能");
}
public void f(){
throw new UnsupportedOperationException("对象不支持这个功能");
}
}
-
实现类:Ashil
继承适配器类,选择性地重写相应函数。
public class Ashili extends Adapter {
public void a(){
System.out.println("实现A方法被调用");
}
public void d(){
System.out.println("实现d方法被调用");
}
}
何时使用适配器模式?
1.在软件开发后期或是开发结束之后,被调用的一些接口需要换掉,为了不需改已经开发好的代码,可以增加一个适配器,起到连接新接口和旧调用的桥梁。
PS:一般在软件设计、开发过程中,如果发现需要调用的接口和原本定义的接口不匹配,应尽量选择重构接口,而不是使用适配器作为转换。
2.在软件开发过程中,需要调用第三方类库的时候,如果第三方类库提供的接口和我们设计的接口不一致,此时可以使用适配器模式,将第三方类库的接口转换成我们所需要的接口。
总之一句话:在接口的调用和被调用双方都不太容易修改的时候再使用适配器模式。
参考:
(1)三句话搞定适配器模式 https://blog.csdn.net/u010425776/article/details/48007027
(2)适配器模式(Adapter模式)详解 http://c.biancheng.net/view/1361.html
(3)柴毛毛大话设计模式——开发常用的设计模式梳理 https://blog.csdn.net/u010425776/article/details/79211117