• 设计模式-适配器模式


    一、适配器模式的核心思想

    适配器模式的核心思想:把原有的接口转变成调用者期待的接口,从而使不同接口的类可以一起工作。

    适配器中包含如下3个角色:

    • 源角色 Adaptee:需要适配的目标类或接口。
    • 目标角色 Target:所期望得到的接口。
    • 适配器角色 Adapter:适配器类是本模式的核心,用来把源接口转换成目标接口,显然这-角色不可以是接口,而必须是具体类。

    这3者角色之间的交互关系便组成了适配器模式的模型,如下图所示。
    在这里插入图片描述
    Adaptee 类只有 operation()方法,没有 newoperation()方法,但是客户端又需要目标类同时拥有这两个方法,这时便可以新建一个接口 Target,并提供一个中间环节 Adapter类,Adapter 类实现了 Targe接口,并继承自 Adaptee,Adapter 类的 operation()方法重新封装了 Adapter 类的 operation()方法,并同时实现了 newoperation()方法,这便实现了适配的目的。

    二、第一种:类的适配器模式(对类进行适配)

    第一种模式是类的适配器模式,它用来对目标类进行包装,如下图所示。
    在这里插入图片描述

    • Source 类是具体的原始类,是待适配的对象,它拥有一个函数operation1()。
    • Targetable 是要适配的目标接口,它拥有一个与 Source 同样的接口函数 operation1(),并提供了一个新的接口函数operation2(),该函数是要扩展的功能。
    • Adapter 是适配器类,它必须继承Source类,并实现 Targetable接口,从而将 Source 的功能扩展到 Targetable 接口所具有的功能。

    适配后的 Source 类,即可以通过调用 Targetable 接口来实现对 Source 类的操作。

    下面来看具体的实现。

    (1) Source 类的源代码如下程序所示,其默认的操作函数 operation1()用来输出一个字符串

    源类 Source.iava

    package structure.adapter;
    /**
    * @author Minggg
    * 源类
    */
    public class Source {
    	public void operation1(){
    		System.out.printn("原始类的方法"):
    	}
    }
    

    (2) Targetable必须首先具备与 Source 相同的函数接口,这样它才能够实现 Source 功能,然后增加扩展函数operation2()。其源代码如下程序 所示。

    目标接口 Targetable.java

    package structure.adapter;
    
    /**
    * @author Minggg
    * 日标接口
    */
    public interface Targetable {
    	/**
    	* 与源类相同的接口函数事
    	*/
    	public void operation1();
    	/**
    	* 新的接口函数,源类中没有
    	*/
    	public void operation2():
    
    }
    

    (3) Adapter的作用是将 Source 适配为Targetable,因此它必须继承 Source 类并实现 Targetable接口,这样它便拥有了 Source 函数 operation1()的功能,该函数还拥有了与 Targetable 同样的接口;它必须实现 Targetable 的扩展函数,它往控制台输出了一个字符串。其源代码如下程序所示。

    适配器类 Adapter.iava

    package structure.adapter;
    /**
    * @author Minggg
    * 适配器模式,继承源类,并实现目标接口
    */
    public class Adapter extends Source implements Targetable {
    
    	/**
    	* 实现目标类的新接口函数
    	*/
    	public void operation2() {
    		System.out.println("适配目标类后的方法");
    	}
    }
    

    (4) 我们就可以创建一个 Adapter 类的对象,该对象属于 Targetable,调用该对象的 operation1()函数将会实现对 Source 函数的调用,调用 operation2()函数将会实现对 Adatper 实现函数的调用,这样就实现了对类 Source 到 Targetable 的适配。测试类源代码如下程序所示。

    测试类AdapterTest.java

    package structure.adapter;
    
    /**
    * @author Minggg
    * 按照目标接口来创建实例,并调用该接口的各个实现函数
    */
    public class AdapterTest {
    
    	public static void main(Stringl] args)
    		// 创建目标接口的类实例
    		Targetable obj= new Adapter();
    		
    		// 调用目标类的方法
    		obj.operation1();
    		obj.operation2();
    	}
    }
    

    运行该程序的输出为:

    原始类的方法
    适配目标类后的方法
    

    这正是我们想要的结果:把类Source按照接口Targetable 进行调用。

    三、第二种:对象的适配器模式(对对象进行包装)

    第二种模式是对象的适配器模式,它用来对目标对象进行包装,因此又叫做包装器模式,如下图所示。
    在这里插入图片描述

    • Source 类是具体的原始类,是待包装对象的类,它拥有一个函数operation1()。
    • Targetable 是要适配的目标接口,它拥有一个与 Source 同样的接口函数 operation1(),并提供了一个新的接口函数 operation2(),该函数是要扩展的功能。
    • Wrapper 是包装器类,它与Adapter适配器不同,它不需要继承Source,但必须拥有一个Source 类型的对象 source,该对象在构造函数中被赋值。在该包装器的operation1()函数中需要调用 source 的 operation1(),这样就实现了对原有对象的调用;同时扩展实现 Targetable的 operation2()函数。

    包装后的 Wrapper 类,即可以通过调用Targetable 接口来实现对 Source 类的操作。

    以上的 Source 类和 Targetable 接口与第一种模式相同,唯一不同的是 Wrapper 的实现方式不同。其源代码如下程序所示:

    包装器类Wrapper.java

    package structure.adapter;
    
    /**
    * @author Minggg
    * 包装器模式
    */
    public class Wrapper implements Targetable {
    
    	private Source source;
    	
    	/**
    	* 取得源类对象
    	*/
    	public Wrapper(Source source) {
    		super();
    		this.source = source;
    	}
    	
    	/**
    	* 调用源类对象的方法
    	*/
    	public void operation1() {
    		source.operation1();
    	}
    	
    	/**
    	* 实现目标类的新接口函数
    	*/
    	public void operation2(){
    		System.out.println("包装目标类后的方法");
    	}
    }
    
    

    创建一个 Source 类的对象 source,并根据该对象创建一个 Wrapper 包装器obj,该包装器对象属于 Targetable,调用该对象的 operation1()函数将会实现对 Source 函数的调用,调用 operation2()函数将会实现对 Wrapper 实现函数的调用,这样就实现了对类 Source 到 Targetable 的包装。测试类源代码如下程序所示:

    测试类WrapperTest.java

    package structure.adapter;
    
    /**
    * @author Minggg
    * 按照目标接口来创建实例,并调用该接口的各个实现函数
    */
    public class WrapperTest {
    	public static void main(String[] args){
    		// 创建源类对象
    		Source source = new Source();
    		// 创建source 的包装类对象
    		Targetable obj= new Wrapper(source);
    		// 调用目标类的方法
    		obj.operation1();
    		obj.operation2();
    	}
    }
    

    运行该程序的输出为:

    原始类的方法
    包装目标类后的方法
    

    输出的结果与第一种模式相同,因此效果是与第一种模式相同的,不同的是适配的方式不同。

    四、第三种:接口的适配器模式(对接口抽象化)

    有时我们会在一个接口中定义多个接口方法,如果要实现该接口编写一个类,就必须为每一个接口方法编写实现代码,这显然会造成很大的浪费。为了解决这个问题,可以使用第三种适配器模式–默认适配器。它会为原有的接口类实现一个默认的抽象类,在该抽象类中编写每一个接口的默认实现,当我们需要编写一个具体类时,只需要继承自该抽象类,而不需要实现原有的接口。并且,此时我们不需要实现所有的接口方法,只实现需要的函数即可。

    如下图所示为接口的适配器模式。
    在这里插入图片描述

    • Sourceable是定义了多个接口函数的接口类。
    • DefaultWrapper 是一个抽象类,它实现了接口 Sourcable,并为每一个接口函数提供了默认的实现。

    依据 DefaultWrapper 就可以编写不同的实现,在实现中只需要重写部分待实现的函数,而不需要重写全部。

    下面来看具体的实现。
    (1) Sourcable类的源代码如下程序所示,它定义了两个操作函数。

    源接口 Sourcable.java

    package structure.adapter;
    
    /**
    * @author Minggg
    * 源接口
    */
    public interface Soureable {
    	public void operation1();
    	public void operation2();
    }
    

    (2) DefaultWrapper 实现了Sourcable 接口,并提供了其两个接口函数的实现,在该实现中可以什么也不做,目的只是为了给其子类提供一个默认的实现。其源代码如下程序所示。

    默认适配器类 DefaultWrapper.java

    package structure.adapter;
    
    /**
    * @author Minggg
    * 包装器模式
    */
    public abstract class DefaultWrapper implements Sourcable {
    	public void operation1(){}
    	public void operation2(){}
    }
    

    (3) SourceSub1 继承自DefaultWrapper,由于DefaultWrapper 的屏蔽作用,SourceSub1 可以只重新实现自己关心的函数 operation1(),它负责输出一个字符串。其源代码如下程序所示。

    源接口的实现子类 SourceSub1.java

    package structure.adapter;
    
    /**
    * @author Minggg
    * 源接口的实现类
    */
    public class SourceSub1 extends DefaultWrapper {
    	public void operation1(){
    		System.out.println("源接口的一个实现子类 Sub1");
    	}
    }
    

    (4) SourceSub2继承自DefaultWrapper,由于DefaultWrapper 的屏蔽作用,SourceSub2 可以只重新实现自己关心的函数 operation2(),它负责输出一个字符串。其源代码如下程序所示。

    源接口的实现子类 SourceSub2.java

    package structure.adapter;
    
    /**
    * @author Minggg
    * 源接口的实现类
    */
    public class SourceSub2 extends DefaultWrapper {
    	public void operation2(){
    		System.out.println("源接口的一个实现子类 Sub2");
    	}
    }
    

    以上我们编写了两个实现SourceSub1和SourceSub2,下面分别创建一个对象,创建的对象都属于 Sourcable。然后分别调用它们的方法 operation1()和 operation2()。其源代码如下程序所示。

    测试类 DefaultWrapperTest.java

    package structure.adapter;
    
    public class DefaultWrapperTest {
    
    	public static void main(String[] args){
    		Sourcable source1 = new SourceSub1();
    		Sourcable source2 = new SourceSub2();
    		source1.operation1();
    		source1.operation2();
    		source2.operation1();
    		source2.operation2();
    	}
    }
    

    运行该程序的结果如下:

    源接口的一个实现子类 Sub1
    源接口的一个实现子类 Sub2
    

    从输出的结果可以看出,source1和source2仅仅在运行自身实现的函数时发生了作用,对于没有实现的函数则调用了默认适配器 DefaultWrapper 的默认函数,什么也没有输出。

    五、何时使用适配器模式

    从以上的讲解我们已经知道,如果需要将一个类变成另一个类时就可以使用适配器模式。但是根据需求的不同,可以分别选用3种不同的子模式。

    • 类的适配器模式:当希望将一个类转换成满足另一个接口时,可以模仿Adapter的做法来构造一个新的适配器类,该类继承原有的类并实现新的接口即可。
    • 对象的适配器模式:当希望将一个对象转换成另一个接口时,可以模仿Wrapper的做法来构造一个新的包装类,该类调用原有的类并实现新的接口即可。
    • 默认适配器模式:当不希望实现一个接口的所有方法时,可以模仿DefaultWrapper 的做法构造一个抽象类,给出所有方法的默认的实现,这样,从这个抽象类再继承下去的子类不必实现所有的方法了。

    以上的说法稍显抽象,在Java API中有许多地方都运用了适配器模式,下面我们找出来分析下,你就会明白适配器模式的实际用处了。

    六、Java 中的应用–Iterator 适配器(对象的适配器模式)

    在Java中存在两个迭代器类:Iterator和Enumeration。两者都可以由相应的集合对象转化而来,例如 ArrayList 可以变成 Iterator 对象,Vector 可以变成 Enumeration 对象。然而,有时你可能需要将ArrayList 转换成 Enumeration 对象,这就需要使用包装器模式来将 Iterator 对象转换成 Enumeratior对象了。

    此时我们可以选择第二种适配器–对象的适配器。如图下所示,包装器Itermeration 负责将对象Iterator改造成Enumeration的形式。

    在这里插入图片描述
    Iterator 对象拥有如下两个函数:

    • hasNext()判断是否有下一个对象。
    • next()取得下一个对象。

    为了将 Iterator 转换为 Enumeration,此时可以调用上面的两个函数来分别实现 Enumeration 的如下两个函数:

    • hasMoreElements()判断是否有下一个对象。
    • nextElement()取得下一个对象。

    其完整的代码如下程序所示。

    Iterator被包装为Enumeration类型Itermeration.java

    package structure.adapter;
    
    import java.util.Iterator;
    import java.util.NoSuchElementException;
    import java.util.Enumeration;
    
    /**
    * @author Minggg
    * 使用了包装器模式
    */
    public class Itermeration implements Enumeration<Object>{
    	Iterator<Object> it;
    
    	public Itermeration(Iterator<Object> it){
    		this.it = it;
    	}
    
    	public boolean hasMoreElements(){
    		return it.hasNext();
    	}
    	
    	public Obiect nextElement()throws NoSuchElementException {
    		return it.next();
    	}
    
    }
    

    下面来编写测试代码。首先创建一个 ArayList 对象并初始化数据,进而得到Iterator 对象,然后根据该对象构造 Itermeration 的对象即可,最后即可按照 Enumeration 的接口进行迭代。其源代码如下程序所示。

    测试类ItermerationTest.java

    package structure.adapter;
    import java.util.ArrayList;
    import java.util.Enumeration;
    import java.util.Iterator;
    import java.util.List;
    
    public class ItermerationTest {
    	public static void main(String[] args){
    		//创建Iterator 对象
    		List<Object> list = new ArrayList<Object>();
    		list.add("aaa");
    		list.add("bbb");
    		list.add("ccc");
    		Iterator<Object> it = list.iterator();
    		//取得Enumeration 对象
    		Enumeration<Object> em = new ltermeration(it);
    		while(em.hasMoreElements()){
    			Systemout.printn(em.nextElement());
    		}
    	}
    }
    

    运行该程序的结果如下:

    aaa
    bbb
    ccc
    
  • 相关阅读:
    力扣46:全排列(Java回溯)
    实训笔记9.4
    1013 Battle Over Cities
    [极客大挑战 2019]BabySQL
    自动生成VGG图像注释文件
    GAN学习笔记
    Qtday1
    根据json文件生成mask图像
    css:移动端实现1px、0.5px的细线
    视频拍摄教程分享
  • 原文地址:https://blog.csdn.net/qq_35885952/article/details/139645084