前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >图解设计模式:动动手玩转适配器模式

图解设计模式:动动手玩转适配器模式

作者头像
冷环渊
发布2022-12-18 18:15:55
3050
发布2022-12-18 18:15:55
举报

前言

☀️☀️点开就是缘分认识一下,我是小冷。是一个兴趣驱动自学练习两年半的的Java工程师。 一位十分喜欢将知识分享出来的Java博主⭐️⭐️⭐️,擅长使用Java技术开发web项目和工具 文章内容丰富:覆盖大部分java必学技术栈,前端,计算机基础,容器等方面的文章 如果你也对Java感兴趣,关注小冷吧,一起探索Java技术的生态与进步,一起讨论Java技术的使用与学习 ✏️高质量技术专栏专栏链接: 微服务数据结构netty单点登录SSMSpringCloudAlibaba等 ⏩当前专栏设计模式系列专栏代码地址: 代码地址

适配器模式 Aadpter

这个模式其实在生活中很好就能找到案例:

比如我们的笔记本电脑 都会有一个电源适配器来基于我们笔记本进行供电。

image-20221215200630916
image-20221215200630916

在程序中,经常会现有的程序无法直接使用,会出现需要做适当的变化之后才可以使用到情况。折中用于填补现有程序和所需程序之间差异的设计模式就是Adapter模式

Adapter模式也被称为Wrapper模式,wrapper有包装器的意思,就像用精美的包装纸把商品包装成礼物那样,替我们把代码封装起来,使其可以用于其他用途

Adapter模式有以下两种:

  • 类适配器模式(使用继承的适配器)
  • 对象适配器模式(使用委托的适配器)

类适配器模式

实现思路

首先,让我们来看一段使用继承的适配器的示例程序。这里的示例程序是一段会将输入的字符 串显示为(Hello)或是&Hello&的简单程序。

目前在Banner类中,有将字符用括号括起来的showWithParen方法,和将字符串用&号括起来的showwithAster方法

假设Print接口中声明了两种方法,即字符串显示(加括号)的printWeak方法,和调字符串显示(加&号)的printstrong方法。

现在要做的事情是使用Banner类编写一个实现了Print接口的类,也就是说要做一个将 内容转换的适配器。

扮演适配器角色的是PrintBanner类。该类继承了Banner类并实现了“需求”——Print 接口。PrintBanner类使用showWithParen方法实现了printWeak,使用showwithAster 方法实现了printstrong。这样,PrintBanner类就具有适配器的功能了。

类的对应图
image-20221215221545495
image-20221215221545495

使用了类适配器的示例程序的类图(继承)

image-20221215221615955
image-20221215221615955
示例代码

像上面举例的笔记本电脑的例子

  • banner类 就像是输出 100伏特电压的插座口
  • print接口 就像是输出12伏特电压的输出口
  • printBanner类就像适配器一样,让我们只需要调用print接口就可以拿到banner类的结果
Banner类

作为基类,有提供输出两种不同内容的方法,

代码语言:javascript
复制
public class Banner {
    private String string;

    public Banner(String string) {
        this.string = string;
    }

    public void showWithParen(){
        System.out.println("("+string+")");
    }
    public void showWithAster(){
        System.out.println("&"+string+"&");
    }
}
Print接口

可以理解为 适配器的一端 提供两种输出方式

代码语言:javascript
复制
public interface Print {
    public abstract void printWeak();
    public abstract void printStrong();
}
printBanner类

作为适配器 让需要使用到类只需要调用 print 就可以拿到 banner的结果

代码语言:javascript
复制
public class printBanner extends Banner implements Print {
    public printBanner(String string) {
        super(string);
    }

    @Override
    public void printWeak() {
     showWithParen();
    }

    @Override
    public void printStrong() {
        showWithAster();
    }
}
测试用例
代码语言:javascript
复制
    public static void main(String[] args) {
        Print printBanner = new printBanner("hello");
        printBanner.printWeak();
        printBanner.printStrong();
    }
image-20221215225429498
image-20221215225429498
总结

在main方法中 我们使用到时print 接口,对于main来说 banner对于我们来说是完全隐藏起来的,就像是笔记本只要能在直流12伏特电压下就可以正常工作,他并不知道这12伏特是由适配器将100伏特电压转换而成的。

main类是不知道printBanner类是如何实现的,这样就可以在不用对main类进行修改的情况下,改变printBanner类

使用委托方式的适配器模式

上一个示例我们用类适配器模式了,下面我们再来看看对象适配器模式,在之前的示例程序中,我们用继承实现适配,在这里我们用 “委托” 来实现

PS: 委托可以理解为 把事情交给别人干

类图

类图也是发生了变化 printbanner实例化了banner来做一些事情

image-20221215225846504
image-20221215225846504
示例
代码语言:javascript
复制
public class printBanner1  implements Print {
    private Banner banner;
    public printBanner1(String string) {
        this.banner = new Banner(string);
    }

    @Override
    public void printWeak() {
        banner.showWithParen();
    }

    @Override
    public void printStrong() {
        banner.showWithAster();
    }
}

只需要修改PrintBanner就可以了,这里没有采用继承而是直接生成了banner的示例去调用banner的方法

在Adapter适配器模式中的角色

  • Target 对象 这个角色定义了所需要的方法 print 依照文章开头的例子来说 就是需要使用到12伏特电源
  • Client 使用者 这个角色负责使用被适配后的内容,main 依照例子就是需要使用12伏特电源的电脑
  • Adaptee 被适配 这个角色是一个持有既定方法的角色 依照例子就是一百伏特的插头,由banner扮演此角色,当被适配的角色和target角色方法相同,也就是家庭电压就是12伏特那么就不需要这个角色
  • Adapter 适配器模式的主人公 pringBanner主要负责兼具两侧的功能作为混合,用Adaptee的角色来满足target的要求,这就是适配器模式的目的

拓展思路

什么时候我们需要用上Adapter模式

如果某个方法就是我们所需要的方法,那么直接在程序中使用不就可以了? 很多时候进场会用到的类

实际上,我们在让现有的类适配新的接口时,常常会有 “只要将这里稍微修改一下就可以了” 的想法,一不留神就会修改现有代码,需要注意的是,如果要对已经测试完毕的现有代码进行修改,就必须要在修改后重新测试

Adapter 模式会对现有的类进行适配,生成新的类,通过该模式很方便创建我们需要的方法区,当出现bug的时候,我们明确的知道bug不在现有的类(Apdaptee 角色)中,所以只需要调查Adapter角色的类即可,这样代码的可维护性也提高了

如果没有线程的代码

让现有类适配新的接口时,使用适配器类貌似是一种很不错的解法,但是实际上我们遇到现有类适配新接口时,尝尝会有“只要修改一下就可以使用到”的想法,就会修改现有代码,需要注意的是如果要对已经测试完毕的代码进行修改就会又要测试

兼容性与升级性

软件的生命周期总是版本的升级,当版本升级的时候经常会出现与旧版本的兼容性问题,如果能够完全抛弃旧版本,那么软件维护起来也会轻松一些,但是在日常开发中往往无法抛弃旧版本,这个时候就可以使用适配器版本来做新旧版本的兼容

​ 假设我们今后指向维护新版本那么只需要维护新版本的Adaptee角色,旧版本扮演target角色

拓展示例

Q: 我们为什么用print来使用printbanner的功能 而不是直接用printbanner呢?

A:我们使用print的原因是只需要使用print里的方法,其实用哪一种都是一样,printbanner里的可能还有其他的方法,

拓展代码

请使用适配器模式编写一个将属性集合保存到文件中的FileProperties类中。

这里,我们假设代码的FileIO接口声明了FileProperties需要实现的方法,输入文件file.txt和输出文件newfile.txt的内容如下

输入文件

代码语言:javascript
复制
yaer= 1999

输出文件

代码语言:javascript
复制
day=21
yaer=2004
month=4

main方法为

代码语言:javascript
复制
    public static void main(String[] args) {
        FileIO f = new FileProperties();
        try {
            f.readFromFile("D:\\JavaEngineer\\DesignPatterns\\DesignPattern\\DesignPattern\\src\\main\\java\\hyc\\Adapter\\expand\\file.txt");
            f.setValue("year","2004");
            f.setValue("month","4");
            f.setValue("day","21");
            f.writeToFile("D:\\JavaEngineer\\DesignPatterns\\DesignPattern\\DesignPattern\\src\\main\\java\\hyc\\Adapter\\expand\\newfile.txt");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

fileio类

代码语言:javascript
复制
public interface FileIO {
    void readFromFile(String filename) throws IOException;
    void writeToFile(String filename) throws IOException;
    void setValue(String key,String value);
    String getValue(String key);

}

fileProperties 作为适配fileio和Properties的类 Properties为java.util下的Proerties工具类

代码语言:javascript
复制
public class FileProperties extends Properties implements FileIO {


    @Override
    public void readFromFile(String filename) throws IOException {
        load(new FileInputStream(filename));
    }

    @Override
    public void writeToFile(String filename) throws IOException {
    store(new FileOutputStream(filename),"written by FileProperties");
    }

    @Override
    public void setValue(String key, String value) {
        setProperty(key,value);
    }

    @Override
    public String getValue(String key) {
        return getProperty(key);
    }
}

file文件有上述内容 newfile为空 此时我们执行代码

image-20221216001446993
image-20221216001446993

运行结果

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-12-16 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 适配器模式 Aadpter
    • 类适配器模式
      • 类的对应图
      • 示例代码
      • 总结
    • 使用委托方式的适配器模式
      • 类图
      • 示例
    • 在Adapter适配器模式中的角色
      • 拓展思路
        • 什么时候我们需要用上Adapter模式
        • 如果没有线程的代码
        • 兼容性与升级性
      • 拓展示例
        • 拓展代码
        相关产品与服务
        容器服务
        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档