适配器模式

        适配器模式可以将一个类的接口和另一个类的接口匹配起来,而无需修改原来的适配者接口和抽象目标类接口。
在这里插入图片描述        适配器模式的别名为包装器(Wrapper)模式,它既可以作为类结构型模式,也可以作为对象结构型模式。在适配器模式的定义中所提及的接口是指广义的接口,它可以表示一个方法或方法的集合。

1.适配器模式结构

        适配器模式包括类适配器和对象适配器。在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系,下面分别分析这两种适配器的结构。
        类适配器模式的结构图如图1所示,对象适配器模式的结构图如图2所示。适配器模式包含以下三个角色。
        (1)Target(目标抽象类):目标抽象类定义客户所需的接口,可以是一个抽象类或接口,也可以是具体类。在类适配器中,由于Java语言不支持多继承,他只能是接口。
        (2)Adapter(适配器类):它可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配。适配器Adapter是适配器模式的核心,在类适配器中,它通过实现Target接口并继承Adaptee类来使二者产生联系,在对象适配器中,它通过继承Target并关联一个Adaptee对象是二者产生联系。
        Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下甚至没有适配者类的源代码。


图1.类适配器模式结构图

图1.对象适配器模式结构图

2.适配器模式实现

        由于适配器模式包含类适配器模式和对象适配器模式两种形式,下面分别介绍这两种适配器模式的实现机制。

2.1.类适配器

        如图1所示的类适配器模式结构图,在类适配器中适配者类Adaptee没有request()方法,而客户端期待这个方法,但这适配器者类中实现specificRequest()方法,该方法提供的实现正是客户端所需要的。为了使客户端能够使用适配者类,提供了一个中间类,即适配器类Adapter(),适配器类实现了抽象目标类接口Target,并继承了适配者类,在适配器类的request()方法中调用所继承的适配者类的specificRequest()方法,达到了适配的目的。因为适配器类与适配者类是继承关系,所有这种适配器模式被称为类适配器模式。典型的类适配器代码如下:

package cassadapterpattern;

//类适配器
public class Adapter extends Adaptee implements Target {
    @Override
    public void request() {
        speificRequest();
    }
}

2.2.对象适配器

        如图2所示的对象适配器模式结构图,在对象适配器中客户端需要调用request()方法,而适配者类Adaptee()没有该方法,但是它提供的specificRequest()方法却是客户端所需要的。为了使客户端能够使用适配者类,需要提供一个包装类Adapter(),即适配器类。这个包装类包装了一个适配者的实例,从而将客户端与适配者衔接起来,在适配器的request()方法中调用适配者的specificRequest()方法。因为适配器类与适配者类是关联关系(也可称为委派关系),所有这种适配器模式称为对象适配器模式。典型的对象适配器代码如下:

package objectadapterpattern;

//对象适配器模式
public class Adapter extends Target{
    //维持一个对适配者对象的引用
    private Adaptee adaptee;
    public Adapter(Adaptee adaptee){
        this.adaptee=adaptee;
    }
    //转发调用
    @Override
    public void request(){
        adaptee.specificRequest();
    }
}

        适配器模式可以将一个类的接口与另一个类的接口匹配起来,使用的前提是不能或不想修改原来的适配者接口和抽象目标类接口。例如购买了一些第三方类库和控件,但是没有源代码,此时使用适配器模式可以统一对象接口访问。
        适配器模式更多的是强调对代码的组织,而不是功能的实现。

2.适配器模式应用实例

在这里插入图片描述


图3.汽车控制软件结构图
        在图3中,CarController类充当抽象目标,PoliceLamp和PoliceSound类充当适配者,PoliceCarAdapter充当适配器。

        (1)CarController:汽车控制类,充当目标抽象类。

package adapterpatternexample;

public abstract class CarController {
    public void move(){
        System.out.println("玩具汽车移动");
    }
    public abstract void phonate();
    public abstract void twinkle();
}

        (2)PoliceSound:警笛类,充当适配者。

package adapterpatternexample;

public class PoliceSound {
    public void alarmSound(){
        System.out.println("发出警笛声音");
    }
}

        (3)PoliceLamp:警灯类,充当适配者。

package adapterpatternexample;

public class PoliceLamp {
    public void alarmLamp(){
        System.out.println("呈现警笛闪烁");
    }
}

        (4)PoliceCarAdapter:警车适配器,充当适配器。

package adapterpatternexample;

public class PoliceCarAdapter extends CarController{
    //定义适配者PoliceSound对象
    private PoliceSound policeSound;
    //定义适配者PoliceLamp对象
    private PoliceLamp policeLamp;
    public PoliceCarAdapter(){
        policeSound=new PoliceSound();
        policeLamp=new PoliceLamp();
    }
    //发出警笛声音
    @Override
    public void phonate() {
    //调用适配者类PoliceSound的方法
        policeSound.alarmSound();
    }
    //呈现警笛闪烁
    @Override
    public void twinkle() {
    //调用适配者类PoliceLamp的方法
        policeLamp.alarmLamp();
    }
}

        (5)配置文件config.xml,,在配置文件中存储了适配器类的类名

<?xml version="1.0" encoding="UTF-8" ?>
<config>
    <className>adapterpatternexample.PoliceCarAdapter</className>
</config>

        (6)XMLUtil:工具类

package adapterpatternexample;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;

public class XMLUtil {
    //该方法用于从XML配置文件中提取具体类的类名,并返回一个一个实例对象
    public static Object getBean(){
        try {
            //创建dom文档对象
            DocumentBuilderFactory documentBuilderFactory=DocumentBuilderFactory.newInstance();
            DocumentBuilder documentBuilder=documentBuilderFactory.newDocumentBuilder();
            Document document=documentBuilder.parse(new File("src/main/java/adapterpatternexample/config.xml"));
            //获取包含类名的文本节点
           NodeList nodeList=document.getElementsByTagName("className");
           Node node=nodeList.item(0).getFirstChild();
           String cName=node.getNodeValue();
            //通过类名生成实例对象并返回
            Class c=Class.forName(cName);
            Object object=c.newInstance();
            return object;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }



}

        (7)客户端测试类

package adapterpatternexample;

public class Client {
    public static void main(String[] args){
        CarController carController=(CarController)XMLUtil.getBean();
        carController.move();
        carController.phonate();
        carController.twinkle();
    }
}

        编译并运行程序,输出结果如下:
在这里插入图片描述        在本实例中使用了对象适配器模式,同时引入了配置文件,将适配器类的类名存储在配置文件config.xml中。如果需要使用其他声音类或者灯光类,可以增加一个新的适配器,使用新的适配器来适配新的声音类或者灯光类,原有代码无须修改。通过引入配置文件和反射机制可以在不修改客户端代码的情况下使用新的适配器,无须修改源代码,符合开闭原则。
        在本实例中目标抽象类是一个抽象类,而不是接口,并且实例中的适配器类PoliceCarAdapter同时适配了两个适配者,由于Java语言不支持多重继承,因此本实例只能通过对象适配器来实现,而不能使用类适配器。在实际软件开发中对象适配器比类适配器更加灵活,其使用频率更高。

3.适配器模式优点

        (1)将目标类和适配者类解耦,通过引入一个适配者类来重用现有的适配者类,无须修改原有结构。
        (2)增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复原。
        (3)灵活性和扩展性都非常好,通过使用配置文件可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合开闭原则。
        类适配器模式还有以下有点:
        由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。
        对象适配器模式还有以下优点:
        (1)一个对象适配器可以把多个不同的适配者适配到同一个目标。
        (2)可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据里氏代换原则,适配者的子类也可以通过该适配器进行适配。
        总结:Target目标抽象类中的接口是原本使用的接口,但现在为了满足新的需要要更改其具体实现,其具体实现即为Adaptee适配者类方法中的实现(即需要Targte里的方法名和Adaptee里的方法实现),同时又不修改两者(为了满足开闭原则),因此使用适配器类,使两个不兼容的接口能一块工作。如果说非要修改Target里的接口呢?其一违反开闭原则,其二未必有Adaptee的源代码,只能使用适配器类做统一对象访问接口。
参考:Java设计模式(刘伟),图解设计模式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值