设计模式-适配器

 

adapter模式:适配老版本接口 和 第三方接口

场景介绍:

首先举一个场景,这个场景其实还是挺常见的,因为你的系统不断的在迭代,

(1) 假设,我们做了一个第一版的系统,这个系统里有一个接口 和一个实现类;

(2) 接着我们开始做第二版的系统,这个系统我们定义了一个新的接口,和新的实现类;

(3) 但是我们同时希望能够基于第二版的接口,或者说我们同时在第二版的系统中 也要使用第一版系统中定义的那个老接口和老实现类

不用适配器的代码实现

 现在如果不用模式的话,我们写了一套代码,然后,这套代码里面,我们又要用新版本的接口,又要用老版本的接口,这个时候,我们的代码看起来是什么样子的,如下:

public class WithoutAdapterPatternDemo {

    public static void main(String[] args) {
        OldInterface oldObject = new OldInterfaceImpl();
        NewInterface newObject = new NewInterfaceImpl();
        oldObject.oldExecute();
        newObject.newExecute();
    }

    /** 老版本接口 */
    public static interface OldInterface{
        void oldExecute();
    }

    /** 老版本接口的实现类 */
    public static class OldInterfaceImpl implements OldInterface{
        @Override
        public void oldExecute() {
            System.out.println("老版本接口实现的功能逻辑");
        }
    }

    /** 新版本接口 */
    public static interface NewInterface{
        void newExecute();
    }

    /** 新版本接口的实现类 */
    public static class NewInterfaceImpl implements NewInterface{
        @Override
        public void newExecute() {
            System.out.println("新版本接口实现的功能逻辑");
        }
    }

}

先定义一个老版本的接口OldInterface,老版本接口,在这个OldInterface里面,给它一个方法,我们叫做oldExecute();

然后是老版本的实现类:OldInterfaceImpl,重写接口中的方法,OldExecute(),方法里,我们就直接打印一行输出就行了:老版本接口实现的功能逻辑 

然后同样,新版本接口NewInterface,新版本接口的实现类NewInterfaceImpl,接口中的方法newExecute(),实现类中的方法也同样打印输出一下:新版本接口实现的功能逻辑;

再写一个main方法,然后

OldInterface oldObject = new OldInterfaceImpl();
NewInterface newObject = new NewInterfaceImpl();
oldObject.oldExecute();
newObject.newExecute();

这个代码就写完了,执行一下,输出如下:

老版本接口实现的功能逻辑

新版本接口实现的功能逻辑

这里说一下,如果不用任何设计模式,我们的问题在哪儿?问题其实很明显,就是说,我们的新的代码中,融合了新老两套接口,这个是很麻烦的一个事情;

首先,如果你这么干的话,就会导致代码很恶心,因为面向的是规范和风格完全不同的两套接口,然后你的理解和维护的成本就提高了。

其次,假如说,现在都不给你选择使用老版本接口的机会,直接就是,强制性公司规范,要求按照新版本接口来走,你的老版本接口的实现类,就没法用了啊?

难不成还要重新写一套,还要基于新版本的接口重新写一套?当然不可能了,所以要用适配器模式。

适配器的代码实现:

我们就用一个新版本的接口,有一个新版本接口的实现类,而且我们还要面向新版本的接口去开发,通过新版本的接口,同时还要使用老版本的这个实现类里面的功能逻辑。这时,我们就需要定义一个适配器类,如果我们要面向新接口NewInterface,去使用老版本接口的实现类oldObject,那么我们可用通过适配器

NewInterface oldObject = new NewInterfaceAdapter(new OldInterfaceImpl());

 将老版本接口的实现类 作为参数,通过适配器 适配到新版本接口中,然后就可以通过 调用新版本接口中的方法,去调用老板本接口实现类中,与其同名方法的功能逻辑。从而达到 面向一套接口,执行一样的方法,却调用了不同实现类中的功能逻辑 的目的。代码如下

public class AdapterPatternDemo {

    public static void main(String[] args) {
        NewInterface oldObject = new NewInterfaceAdapter(new OldInterfaceImpl());
        NewInterface newObject = new NewInterfaceImpl();
        oldObject.newExecute();
        newObject.newExecute();
    }

    /** 定义一个适配器类 */
    public static class NewInterfaceAdapter implements NewInterface{

        private OldInterface oldObject;

        public NewInterfaceAdapter (OldInterface oldObject){
            this.oldObject = oldObject;
        }

        @Override
        public void newExecute() {
            oldObject.oldExecute();
        }
    }

    /** 老版本接口 */
    public static interface OldInterface{
        void oldExecute();
    }

    /** 老版本接口的实现类 */
    public static class OldInterfaceImpl implements OldInterface {
        @Override
        public void oldExecute() {
            System.out.println("老版本接口实现的功能逻辑");
        }
    }

    /** 新版本接口 */
    public static interface NewInterface{
        void newExecute();
    }

    /** 新版本接口的实现类 */
    public static class NewInterfaceImpl implements NewInterface {
        @Override
        public void newExecute() {
            System.out.println("新版本接口实现的功能逻辑");
        }
    }

}

综上,所谓的适配器模式,简单的理解就是,

你手上有新老两接口 和 一个老接口的实现类,但是 现在系统中,要强制面向新接口来开发,那么这个老接口的实现类,就不能直接用了,不能面向老接口来开发了。

这时 就可以开发一个老接口到新接口的适配器,这个适配器是实现了新接口的,但是适配器中持有老接口实现类实例的引用,

然后,适配器的新接口方法的实现,全部基于老接口实现类的老方法来实现即可。

那么,对于调用方而言,只要使用适配器来开发,就可以通过面向新接口开发,但是底层使用老接口的实现类。

 在实际企业开发中的使用场景:

 1. 一般在一套系统不断升级的过程中,比如从v1.0升级到v2.0,v2.0升级到v3.0,有这种版本升级的时候,要把老接口适配成新接口,那么后面的代码,就都可以面向新接口来开发。

 2. 对于一些已有的第3方类库,比如redis客户端,或者是elasticsearch的客户端,它们都提供了一套自己的API,但是我们这里的需求是,需要面向我们这里的一套接口(比如通用的DAO数据访问接口)来进行编程。例如,我们的DAO接口,要求的接口风格都是:save、update、remove、list、get等,这些方法风格。然后,我们现在又搞了一个,比如说DAORedisImpl这么一个类,然后这个redis本身的这个客户端,提供的都是:get、set、、mset、mget等,这样的一套接口风格。那么,这时我们开发的DAORedisImpl,就是一个适配器,这个适配器实现的是我们的DAO接口,在我们的sava、update、remove等方法中,然后去调用redis客户端的get、set、mset、mget等方法。这样,就把redis客户端的这个接口,适配到了我们项目本身 通用的这个DAO接口上面来,那么 类似这个DAORedisImpl这样的类,它就是一个适配器。

适配器模式,一般会随着项目开发过程中,随着功能的扩展,逐步引入很多的外部依赖,大量的使用,而不只是mysql,因为如果用mysql的话,它就是基于mybatis的那个mapper接口去做。

3. 比如com.fazecast:jSerialComm:2.9.1项目中的jSerialComm-2.9.1.jar中,在com.fazecast.jSerialComm.SerialPort.java 里,通过内部类的形式,对OutputStream,jdk本身的抽象类进行了适配:代码如下:

    public final OutputStream getOutputStream() {
        this.outputStream = new SerialPort.SerialPortOutputStream();
        return this.outputStream;
    }

 

    private final class SerialPortOutputStream extends OutputStream {
        private byte[] byteBuffer = new byte[1];

        public SerialPortOutputStream() {
        }

        public final void write(int b) throws SerialPortIOException, SerialPortTimeoutException {
            if (SerialPort.this.portHandle == 0L) {
                throw new SerialPortIOException("This port appears to have been shutdown or disconnected.");
            } else {
                this.byteBuffer[0] = (byte)(b & 255);
                int bytesWritten = SerialPort.this.writeBytes(SerialPort.this.portHandle, this.byteBuffer, 1L, 0L, SerialPort.this.timeoutMode);
                if (bytesWritten < 0) {
                    throw new SerialPortIOException("No bytes written. This port appears to have been shutdown or disconnected.");
                } else if (bytesWritten == 0) {
                    throw new SerialPortTimeoutException("The write operation timed out before all data was written.");
                }
            }
        }

        public final void write(byte[] b) throws NullPointerException, SerialPortIOException, SerialPortTimeoutException {
            this.write(b, 0, b.length);
        }

        public final void write(byte[] b, int off, int len) throws NullPointerException, IndexOutOfBoundsException, SerialPortIOException, SerialPortTimeoutException {
            if (b == null) {
                throw new NullPointerException("A null pointer was passed in for the write buffer.");
            } else if (len >= 0 && off >= 0 && off + len <= b.length) {
                int numWritten;
                for(int totalNumWritten = 0; totalNumWritten != len; totalNumWritten += numWritten) {
                    if (SerialPort.this.portHandle == 0L) {
                        throw new SerialPortIOException("This port appears to have been shutdown or disconnected.");
                    }

                    numWritten = SerialPort.this.writeBytes(SerialPort.this.portHandle, b, (long)(len - totalNumWritten), (long)(off + totalNumWritten), SerialPort.this.timeoutMode);
                    if (numWritten < 0) {
                        throw new SerialPortIOException("No bytes written. This port appears to have been shutdown or disconnected.");
                    }

                    if (numWritten == 0) {
                        throw new SerialPortTimeoutException("The write operation timed out before all data was written.");
                    }
                }

            } else {
                throw new IndexOutOfBoundsException("The specified write offset plus length extends past the end of the specified buffer.");
            }
        }
    }

这里,SerialPortOutputStream.java 就是一个适配器,需要适配的实现直接在SerialPortOutputStream类中重写了OutputStream抽象类中的方法,比如write方法;

 然后,通过直接调用SerialPortOutputStream的无参构造器,获取SerialPortOutputStream适配器实例,就可以直接通过调用OutputStream这个抽象类的中的方法,去调用适SerialPortOutputStream适配器中实现的功能逻辑。与 AdapterPatternDemo 中的适配器 NewInterfaceAdapter 相比,NewInterfaceAdapter 调用了实现类,是通过适配器有参构造器的参数形式,传入到适配器中,在需要重写的适配器方法中间接调用的;而SerialPortOutputStream适配器,是直接重写在是适配器的方法之中。

 

 

 

end

 

posted @ 2022-05-12 23:10  HarryVan  阅读(66)  评论(0编辑  收藏  举报