文章目录
结构型设计模式—适配器模式
1、适配器模式的原理
1.1引题
软件开发过中常遇见的问题
组件更新问题: 现有一个软件系统,其中有一个组件已经过时了,需要从厂商那里引进一个新的组件,但是原系统的组件接口和新组件的接口不同,同时,又不想去改变现有的代码,此时怎样实现从旧组件更新到新组件?
生活中插排的问题
不合适的插座问题:你的电脑的插头是三相的,而墙上的插座只有两相的插头和插座的“接口”不匹配,怎么办?
1.2解决方案
组件更新问题解决方案
生活中插排问题解决方案:
1.3适配器意图
将一个类的接口转换成客户希望的另一个接口。
Adapter模式使的原本由于接口不兼容而不能一起工作的那些类可以一起工作。
2、适配器模式动机和定义
2.1模式动机:
2.1.1通常情况下,客户端可以通过目标类的接口访问它所提供的服务。有时,现有的类可以满足客户类的功能需要,但是它所提供的接口不一定是客户类所期望的,这可能是因为现有类中方法名与目标类中定义的方法名不一致等原因所导致的。
2.1.2在这种情况下,现有的接口需要转化为客户类期望的接口,这样保证了对现有类的重用。如果不进行这样的转化,客户类就不能利用现有类所提供的功能,适配器模式可以完成这样的转化。
2.1.3在适配器模式中可以定义一个包装类,包装不兼容接口的对象,这个包装类指的就是适配器(Adapter),它所包装的对象就是适配者(Adaptee),即被适配的类。
2.4适配器提供客户类需要的接口,适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用。因此,适配器可以使由于接口不兼容而不能交互的类可以一起工作。这就是适配器模式的模式动机。
2.2模式定义
适配器模式(Adapter Pattern):将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼客的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。
适配器图例:
Target——定义了客户使用的特定域的接口
Adapter——适配器,将源角色改造成为目标接口,适配后的对象
Adaptee—一一个需要转换的已有接口
Client——使用适配的应用程序
3、模式分类和结构分析
3.1模式分类:
类适配器模式:类适配器模式通过多重继承对一个接口与另一个接口进行匹配。
对象适配器模式:对象结构型模式关心类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法。(C#、VB.Net.JAVA支持)
类适配器模式的结构图:
类适配器代码实现
package com.qfedu.a_Adepter;
/**
* @Author: chao
* @Date: 2023/03/07/20:54
*/
/**
* 这是客户所期待的接口,目标可以是具体的或抽象类,也可以是接口
*/
interface Target {
void request();
}
/**
* 需要适配的类,被访问和适配的现存组件库中的组件接口
*/
class Adaptee {
public void specificRequest(){
System.out.println("适配者中的业务代码被调用!");
}
}
/**
* 类适配器类
*/
class ClassAdapter extends Adaptee implements Target{
@Override
public void request() {
specificRequest();
}
}
//客户端代码
public class ClassAdapterTest {
public static void main(String[] args)
{
System.out.println("类适配器模式测试:");
Target target = new ClassAdapter();
target.request();
}
}
运行结果
类适配器模式测试:
适配者中的业务代码被调用!
对象适配器模式的结构图
对象适配器代码实现
package com.qfedu.a_Adepter;
/**
* @Author: chao
* @Date: 2023/03/07/20:51
*/
/**
* 对象适配器,通过在内部包装一个 Adaptee 对象,把源接口转换为目标接口
*/
class ObjectAdapter implements Target {
// 建立一个私有的 Adaptee 对象
private Adaptee adaptee;
public ObjectAdapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
// 把表面上调用 request() 方法变成实际调用 specificRequest()
adaptee.specificRequest();
}
}
//客户端代码
public class ObjectAdapterTest {
public static void main(String[] args)
{
System.out.println("对象适配器模式测试:");
// 对客户端来说,调用的就是 Target 的 request()
Adaptee adaptee = new Adaptee();
Target target = new ObjectAdapter(adaptee);
target.request();
}
}
运行结果
对象适配器模式测试:
适配者中的业务代码被调用!
3.2类适配器和对象适配器模式对比:
1、对象适配器通过委派与adaptee衔接,即持有adaptee对象,是动态的方式;类适配器通过集成与adaptee衔接,也就是说类适配器继承adaptee,并且实现target方法,是静态的方式。
2、由于对象适配器采用动态的方式与adaptee衔接,使得它可以对不同的适配源及其子类进行适配
3、类适配器可以重定义实现行为,而对象适配器重定义适配的行为比较困难,但是添加行为较方便。
尽量使用对象适配器的实现方式,多用合成/聚合、少用继承
3.3适配器模式的应用实例
用适配器模式(Adapter)模拟火箭队比赛,教练暂停时给后卫、中锋、前锋分配进攻和防守的方法。
后卫、中锋、前锋都是球员,所有应该有个球员抽象类,有进攻和防守的方法。
package com.qfedu.a_Adepter;
/**
* @Author: chao
* @Date: 2023/03/07/22:07
*/
/**
* 抽象球员类
*/
abstract class Player {
/**
* 球员名字
*/
protected String name;
public Player(String name) {
this.name = name;
}
/**
* 进攻方法
*/
public abstract void attack();
/**
* 防守方法
*/
public abstract void defense();
}
/**
* 前锋
*/
class Forwards extends Player {
public Forwards(String name) {
super(name);
}
@Override
public void attack() {
System.out.println("前锋「0」进攻 " + name);
}
@Override
public void defense() {
System.out.println("前锋「0」防守 " + name);
}
}
/**
* 中锋
*/
class Center extends Player {
public Center(String name) {
super(name);
}
@Override
public void attack() {
System.out.println("中锋「0」进攻 " + name);
}
@Override
public void defense() {
System.out.println("中锋「0」防守 " + name);
}
}
/**
* 后卫
*/
class Guards extends Player {
public Guards(String name) {
super(name);
}
@Override
public void attack() {
System.out.println("后卫「0」进攻 " + name);
}
@Override
public void defense() {
System.out.println("后卫「0」防守 " + name);
}
}
/**
* 外籍中锋
*/
class ForeignCenter {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void attackForeign() {
System.out.println("外籍中锋「0」进攻 " + name);
}
public void defenseForeign() {
System.out.println("外籍中锋「0」防守 " + name);
}
}
/**
* 翻译者
*/
class Translator extends Player {
// 申明并实例化一个内部"外籍中锋"对象,表明翻译者与外籍球员有关联
private ForeignCenter foreignCenter = new ForeignCenter();
public Translator(String name) {
super(name);
}
/**
* 翻译者将"attack"翻译为"进攻"告诉外籍中锋
*/
@Override
public void attack() {
foreignCenter.attackForeign();
}
/**
* 翻译者将"defense"翻译为"防守"告诉外籍中锋
*/
@Override
public void defense() {
foreignCenter.defenseForeign();
}
}
public class PlayerTest {
public static void main(String[] args) {
Player forwards = new Forwards("巴蒂尔");
forwards.attack();
Player center = new Center("科比");
center.attack();
Player guards = new Guards("詹姆斯");
guards.attack();
// 翻译者
Translator translator = new Translator("姚明");
translator.attack();
translator.defense();
}
}
结果
前锋「0」进攻 巴蒂尔
中锋「0」进攻 科比
后卫「0」进攻 詹姆斯
外籍中锋「0」进攻 null
外籍中锋「0」防守 null
4、适配器模式的使用条件和优缺点
4.1使用条件:
1、系统需要使用现有的类,而这些类的接口不符合系统的需要。
2、想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
3、(仅适用于对象Adapter)想使用一个已经存在的子类,但是不可能对于每一个都进行子类化以匹配他们的接口。对象适配器可以适配他的父类接口。
4.2适配器模式优点:
1、将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无需修改原有代码:
2、增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端来说是透明的,而且提高了适配者的复用性;
3、灵活性和扩展性都非常好,通过使用配置文件,而且很方便的增加新的适配器类,完全符合“开闭原则”
4.3适配器模式缺点:
1、适配器编写过程需要结合业务场景全面考虑,可能会增加系统的复杂性。
2、增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱