设计模式学习(二):Adapter适配器模式

news2024/12/23 9:09:22

一、什么是Adapter模式

我们先举个例子:如果想让额定工作电压是直流12V的笔记本电脑在交流220V的电源下工作,应该怎么做呢?通常,我们会使用适配器,将家庭用的交流220V电压转换成我们所需要的直流12V电压。这就是适配器的工作,它位于实际情况与需求之间,填补两者之间的差异。

在程序世界中,经常会存在现有的程序无法直接使用,需要做适当的变换之后才能使用的情况。这种用于填补“现有的程序”和“所需的程序”之间差异的设计模式就是Adapter模式。

Adapter模式也被称为Wrapper模式。Wrapper有“包装器”的意思,就像用精美的包装纸将普通商品包装成礼物那样,替我们把某样东西包起来,使其能够用于其他用途的东西就被称为“包装器”或是“适配器”。

用一句话来概括:Adapter模式就是为程序加一个“适配器”以便于复用。

Adapter模式有以下两种:

  • 类适配器模式(使用继承的适配器)

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

本文将依次介绍这两种Adapter模式。

二、使用继承的Adapter模式示例代码

2.1 各个类之间的关系

先看一下类图:

这里的示例程序是一段会将输入的字符串显示为括号包围或者星号包围的简单程序。例如,输入字符串Hello,显示为(Hello)或是*Hello*。

目前在Banner类( Banner有广告横幅的意思)中,有将字符串用括号括起来的showWithParen方法,和将字符串用*号括起来的showWithAster方法。我们假设这个Banner类是类似前文中的“交流220伏特电压”的“实际情况”。

假设Print接口中声明了两种方法,即弱化字符串显示(加括号)的printweak方法,和强调字符串显示(加*号)的printstrong方法。我们假设这个接口是类似于前文中的“直流12伏特电压”的“需求”。

现在要做的事情是使用Banner类编写一个实现了Print接口的类,也就是说要做一个将“交流220伏特电压”转换成“直流12伏特电压”的适配器。

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

2.2 Banner类

Banner类是我们现有的功能。

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 + "*");
    }
}

2.3 Print接口

Print接口就是我们新的“需求”。

public interface Print {
    public abstract void printWeak();
    public abstract void printStrong();
}

2.4 PrintBanner类

PrintBanner类扮演适配器的角色。

public class PrintBanner extends Banner implements Print{
    public PrintBanner(String string) {
        super(string);
    }

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

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

2.5 用于测试的Main类

public class Main {
    public static void main(String[] args) {
        Print p = new PrintBanner("Hello");
        p.printStrong();
        p.printWeak();
    }
}

2.6 运行结果

需要注意的是,我们是使用Print接口(即调用printweak方法和printstrong方法)来进行编程的。对Main类的代码而言,Banner类中的showWithParen'方法和showWithAster方法被完全隐藏起来了。这就好像需要12V的笔记本电脑插在220V的插座上能正常工作,但它并不知道这12伏特的电压是由适配器将220伏特交流电压转换而成的。

Main类并不知道PrintBanner类是如何实现的,这样就可以在不用对Main类进行修改的情况下改变 PrintBanner类的具体实现。

三、使用委托的Adapter模式示例代码

3.1 各个类之间的关系

先看一下类图:

Main类和 Banner类与示例程序中的内容完全相同,不过这里我们假设Print不是接口而是类。

也就是说,我们打算利用Banner类实现一个类,该类的方法和Print类的方法相同。由于在Java中无法同时继承两个类(只能是单一继承),因此我们无法将PrintBanner类分别定义为Print类和 Banner类的子类。

3.2 Banner类

同上面的Banner

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 + "*");
    }
}

3.3 Print类

public abstract class Print {
    public abstract void printWeak();
    public abstract void printStrong();
}

3.4 PrintBanner类

PrintBanner类的banner字段中保存了Banner类的实例。该实例是在PrintBanner类的构造函数中生成的。然后,printWeak方法和printStrong方法会通过banner字段调用Banner类的showWithParen和 showWithAster方法。

与之前的示例代码中调用了从父类中继承的showWwithParen方法和showwithAster方法不同,这次我们通过字段来调用这两个方法。

这样就形成了一种委托关系。当PrintBanner类的printWeak被调用的时候,并不是PrintBanner类自己进行处理,而是将处理交给了其他实例(Banner类的实例)的showWithParen方法。

public class PrintBanner extends Print{
    private Banner banner;

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

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

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

3.5 用于测试的Main类

同上面的Main

public class Main {
    public static void main(String[] args) {
        Print p = new PrintBanner("Hello");
        p.printStrong();
        p.printWeak();
    }
}

3.6 运行结果

四、拓展思路的要点

4.1 什么时候使用Adapter模式

一定会有读者认为“如果某个方法就是我们所需要的方法,那么直接在程序中使用不就可以了吗?为什么还要考虑使用Adapter模式呢?”那么,究竟应当在什么时候使用Adapter模式呢?

很多时候,我们并非从零开始编程,经常会用到现有的类。特别是当现有的类已经被充分测试过了,Bug很少,而且已经被用于其他软件之中时,我们更愿意将这些类作为组件重复利用。

Adapter模式会对现有的类进行适配,生成新的类。通过该模式可以很方便地创建我们需要的方法群。当出现 Bug时,由于我们很明确地知道Bug不在现有的类( Adaptee角色)中,所以只需调查扮演Adapter角色的类即可。这样一来,代码问题的排查就会变得非常简单。

4.2 如果没有现成的代码

让现有的类适配新的接口(API)时,使用Adapter模式似乎是理所当然的。不过实际上,我们在让现有的类适配新的接口时,常常会有“只要将这里稍微修改下就可以了”的想法,一不留神就会修改现有的代码。但是需要注意的是,如果要对已经测试完毕的现有代码进行修改,就必须在修改后重新进行测试。

使用Adapter模式可以在完全不改变现有代码的前提下使现有代码适配于新的接口(API)。此外,在Adapter模式中,并非一定需要现成的代码。只要知道现有类的功能,就可以编写出新的类。

4.3 版本升级与兼容性

软件的生命周期总是伴随着版本的升级,而在版本升级的时候经常会出现“与旧版本的兼容性”问题。如果能够完全抛弃旧版本,那么软件的维护工作将会轻松得多,但是现实中往往无法这样做。这时,可以使用Adapter模式使新旧版本兼容,帮助我们轻松地同时维护新版本和旧版本。

例如,假设我们今后只想维护新版本。这时可以让新版本扮演Adaptee角色,旧版本扮演Target角色。接着编写一个扮演Adapter角色的类,让它使用新版本的类来实现旧版本的类中的方法。

4.4 功能完全不同的类

当然,当Adaptee角色和Target角色的功能完全不同时,Adapter模式是无法使用的。就如同我们无法用交流220伏特电压让自来水管出水一样。

五、相关的设计模式

5.1 Bridge桥接模式

Adapter模式用于连接接口(API )不同的类,而 Bridge模式则用于连接类的功能层次结构与实现层次结构。

设计模式学习(一):Bridge桥接模式

5.2 Decorator装饰器模式

Adapter模式用于填补不同接口(API)之间的缝隙,而Decorator模式则是在不改变接口(API)的前提下增加功能。

六、思考题

6.1

题目

在示例程序中生成PrintBanner类的实例时,我们采用了如下方法,即使用Print类型的变量来保存PrintBanner实例。

Print p = new PrintBanner ( "Hello");

请问我们为什么不像下面这样使用PrintBanner类型的变量来保存PrintBanner的实例呢?

PrintBanner p = new PrintBanner ( "Hello");

答案

明确地表明程序的意图,即“并不是使用PrintBanner类中的方法,而是使用Print接口中的方法”。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/147225.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Hive数据仓库简介与安装

文章目录Hive数据仓库简介及安装配置一、数据仓库简介1. 什么是数据仓库2. 数据仓库的结构1)数据源2)数据存储与管理3)OLAP服务器4)前端工具3. 数据仓库的数据模型1)星状模型2)雪花模型二、Hive简介1. 什么…

【踩坑总结】解决pycharm下载依赖一直失败的问题

目录前言正文问题复现问题本质解决方案补充总结检查是否安装成功下载的依赖存在哪总结前言 pycharm下载依赖失败这个问题对于我来说已经是个老生常谈的问题,与之共交手三次。 首次交锋是在大二利用 树莓派 做图像采集传输时,在树莓派的ubantu上使用pyt…

docker 容器使用 loki 插件收集日志

相关资料: The log-opts in the /etc/docker/daemon.json is not relaoded - General Discussions - Docker Community ForumsRecently I want to use loki-log-driver to ship logs to the loki server. The docker-deamon is controlled by systemd. The /etc/docker/daemon…

Day2 Spring

1 BeanFactory 与ApplicationContext的关系BeanFactory与ApplicationContext的关系BeanFactory是Spring的早期接口,称为Spring的Bean工厂,ApplicationContext是后期更高级接口,称之为Spring 容器;ApplicationContext在BeanFactory基础上对功能…

Pytorch 多层感知机

一、什么是多层感知机 多层感知机由感知机推广而来,最主要的特点是有多个神经元层,因此也叫深度神经网络(DNN: Deep Neural Networks)。 二、如何实现多层感知机 1、导入所需库并加载fashion_mnist数据集 %matplotlib inline import torch import to…

java弹幕视频网站源码

简介 Java基于ssm的弹幕视频系统,用户注册后可以上传视频进行投稿,也可以浏览视频发送弹幕,在个人中心管理视频、管理弹幕、管理评论等。管理员可以管理视频弹幕评论,查看统计图。 演示视频: https://www.bilibili.c…

CVE-2020-0014 Toast组件点击事件截获漏洞

文章目录前言漏洞分析组件源码触摸属性漏洞利用POC分析漏洞复现漏洞修复总结前言 Toast 组件是 Android 系统一个消息提示组件,比如你可以通过以下代码弹出提示用户“该睡觉了…”: Toast.makeText(this, "该睡觉了…", Toast.LENGTH_SHORT)…

C语言文件操作-从知识到实践全程

目录 引入 文件的打开和关闭 文件如何使用程序来打开? 绝对路径需要转义字符 fopen函数 fclose函数 文件的打开方式(fopen第二参数const char* mode): 文件的顺序读写 fgetc和fputc的使用 fputc fgetc fgets和fputs的使用 fputs fgets perror的使用 fprint…

哪些程序员适合自由工作?(附平台推荐)

在早些时候进行远程办公,接私活或者跨国进行编程,赚点外快等也不是什么奇怪的事情。但是那时候没有人想到会把这些工作完全变成自己的主要业务——也就是我们说的自由工作。也不知道是哪一个第1个吃了螃蟹的人发现自由工作还不错,于是经过后面…

【JavaScript】DOM 学习总结-基础知识

获取元素方法: // 获取三个非常规的标签 console.log(document.documentElement) console.log(document.head) console.log(document.body)通过id/class获取:getElementById / getElementsByClassName // 获取常规的用id,class,tag var boxdocument.g…

Android 自定义Activity的主题

一. 前言 当在某个app中做一个新界面时, 我们要考虑一下主题风格相符合一致. 本篇文章讲解的是,如何新创建的Activity 与整个app主题符合, 特别是状态栏的颜色需要和这个app的状态栏颜色保持一致. 在读本篇文章之前, 可以移步一下笔者之前写的文章:Android style&#xf…

代码随想录算法训练营第十一天字符串 java :20. 有效的括号 1047. 删除字符串中的所有相邻重复项 150. 逆波兰表达式求值

文章目录Leetcode 20. 有效的括号题目详解数据结构 双端队列(deque)Deque有三种用途:思路报错Ac代码Leetcode1047. 删除字符串中的所有相邻重复项题目详解数据结构 ArrayDeque类思路AC代码150. 逆波兰表达式求值题目详解报错难点AC代码收获Leetcode 20. 有效的括号 …

系分 - 系统设计

个人总结,仅供参考,欢迎加好友一起讨论 文章目录系分 - 系统设计考点摘要系统设计软件设计软件架构设计结构化设计概要设计详细设计处理流程设计流程工作流活动及其所有者工作项工作流管理系统WFMS的基本功能WFMS的组成WRM流程设计工具用户界面设计/人机…

python算法与数据结构1-算法、数据结构、链表

目录1、算法的概念1.1 举例:1.2 算法的五大特性:1.3 时间复杂度1.4 空间复杂度2、数据结构2.1 内存的存储结构2.2 数据结构的分类2.3 顺序表存储方式3、链表3.1链表实现3.2链表的方法3.3链表增加节点3.4链表删除节点3.5链表总结1、算法的概念 算法与数据…

(Java高级教程)第三章Java网络编程-第三节:UDP数据报套接字(DatagramSocket)编程

文章目录一:Java数据报套接字通信模型二:相关API详解(1)DatagramSocket(2)DatagramPacket三:UDP通信示例一:客户端发送什么服务端就返回什么(1)代码&#xff…

k8s之ConfigMap和secret

写在前面 我们知道k8s的数据都是存储到kv数据库etcd中的,那么我们程序中使用到各种配置信息是否可以也存储到etcd,然后在pod中使用呢?是可以的,k8s为了实现将自定义的数据存储到etcd,定义了ConfigMap 和secret两种API…

《后端技术面试 38 讲》学习笔记 Day 01

《后端技术面试 38 讲》学习笔记 Day 01 学习目标 在2022年春节将至(半个月),适合在这个冬天里,温故知新。通过学习一门覆盖面较广的课程,来夯实基础,完善自己的知识体系,是一个很棒的选择。 …

LCHub:未来,低代码产品矩阵是500强企业的绝佳选择

近日,国内知名咨询机LCHub发布2022《中国大型企业数字化升级路径研究》。 报告认为由于大型企业的数字化需求旺盛、购买力充足,因此国内成熟的数字化服务商普遍以大型企业为核心客户。大型企业与数字化服务商的供需磨合决定了我国数字化市场的形态,造就了我国数字化市场与海…

go map 源码逐行阅读

map粗略介绍 源码开头注释: A map is just a hash table. The data is arranged into an array of buckets. Each bucket contains up to 8 key/elem pairs. The low-order bits of the hash are used to select a bucket. Each bucket contains a few high-order…

Linux学习笔记——RabbitMQ安装部署

5.4、RabbitMQ安装部署 5.4.1、简介 RabbitMQ是一款知名的开源消息列队系统,为企业提供消息的发布、订阅、点对点传输等消息服务。 RabbitMQ在企业开发中十分常见,课程为大家演示快速搭建RabbitMQ环境。 5.4.2、安装 RabbitMQ在yum仓库中的版本比较老…