接口多态与方法多态

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

在上一篇设计山寨版Stream API时,有一个技巧被频繁使用:接口多态。

接口,用的是函数式接口,即接口内部有且仅有一个抽象方法。

多态,原本指的是接口下有多个子类实例可以指向接口引用,但由于函数式接口恰好仅有一个方法,此时接口多态等同于“方法多态”,即一个抽象方法拥有多个不同的具体实现。

接口多态

我们都知道Java是面向对象的语言,它具备多态性。私以为,多态的精髓在于晚绑定。什么意思呢?

PocketMon pocketMon = new Pikaqiu();
pocketMon.releaseSkill();

只看pocketMon.releaseSkill()你能猜出来技能是电击还是喷火吗?

哦?一眼就看出来了?

这样呢?

Properties pro = new Properties();
FileInputStream in = new FileInputStream("pocketmon.properties");
pro.load(in);
PocketMon pocketMon = Class.forName(pro.getProperty("nextPocketMon")).newInstance();
pocketMon.releaseSkill();

完全看不出来了。

即使你打开pocketmon.properties看了是皮卡丘,运行时虚拟机看到的可能是我修改后的喷火龙。

这种现象其实很奇妙:明明代码都写死了,但虚拟机却无法提前确定具体会是哪只神奇宝贝在调用releaseSkill(),除非实际运行到这行代码。而这,正是得益于多态。

多态的原理,本质是还是JVM层面通过运行时查找方法表实现的。可以简单理解为,JVM在运行时需要去循环遍历这个方法对应的多态实现,选择与当前运行时对象匹配的方法进行调用。所以,从理论上来说,晚绑定的多态在性能上是不如早绑定的(直接写死,不用多态)。而多态是设计模式的灵魂,所以对于一些非常、非常、非常要求性能的场景来说,过于繁重的设计反而会降低性能。说白了,这世上就不存在多、快、好、省。

多态是“晚绑定”思想的体现:对于Java而言,方法的调用并不是编译时绑定,而是运行时动态绑定的,取决于引用具体指向的实例。

方法多态

我生造了“方法多态”这个概念,但这个概念在函数式接口的前提下是站得住脚的,而且有利于跳出面向对象,贴近函数式编程。

我们来看一个需求:

要求写一个cook()方法,传入鸡翅和可乐,你给我做出可乐鸡翅。

很多人可能下意识地就把代码写死了:

public static CokaChickenWing cook(Chicken chicken, Coka coka){1.放油、放姜;2.放鸡翅;3.倒可乐;4.return CokaChickenWing;
}

但是,网上也有人说应该先倒可乐再放鸡翅,每个人的口味不同,做法也不同。有没有办法把这两步延迟确定呢?让调用者自己来安排到底是先倒可乐还是先放鸡翅。

可以这样:

public static CokaChickenWing cook(Chicken chicken, Coka coka, function twoStep){1.放油、放姜;2~3.twoStep();4.return CokaChickenWing;
}

想法很好:既然这两步不确定,那么就由调用者来决定吧,让调用者自己传进来。

我们知道Java是不能直接传递方法的,但利用策略模式可以解决这个问题。

定义一个接口:

interface TwoStep {void excute();
}

然后呢?

public static CokaChickenWing cook(Chicken chicken, Coka coka, TwoStep twoStep){1.放油、放姜;2~3.twoStep.excute();4.return CokaChickenWing;
}

这里twoStep.excute()是确定的吗?

没有。

你说它是先倒可乐,再放鸡翅?我偏要说它是先放鸡翅,再倒可乐!反正接口也没方法体,具体实现要看你传进来什么对象。

所以twoStep.excute()充其量只是先替“某些操作占个坑”,后面再确定。

什么时候确定呢?

main(){TwoStep twoStep = new TwoStep(){@Overridepublic void excute(){2.先放鸡翅3.再倒可乐}}// 调用cook时确定(运行时)cook(chicken, coka, twoStep);
}public static CokaChickenWing cook(Chicken chicken, Coka coka, TwoStep twoStep){1.放油、放姜;2~3.twoStep.excute();4.return CokaChickenWing;
}

来,学过Lambda表达式后,我们换个时髦的写法:

main(){// 调用cook时确定 方案1cook(chicken, coka, (鸡翅, 可乐) -> 2.先放鸡翅,3.再倒可乐);// 调用cook时确定 方案2cook(chicken, coka, (鸡翅, 可乐) -> 2.先倒可乐,3.再放鸡翅);
}public static CokaChickenWing cook(Chicken chicken, Coka coka, TwoStep twoStep){1.放油、放姜;2~3.twoStep.excute();4.return CokaChickenWing;
}

这就是我所谓的“方法多态”:通过函数式接口把形参的坑占住,后续传入不同的Lambda实现各自逻辑。

晚绑定与模板方法模式

在设计模式中策略模式和模板方法看起来有点像,但其实不一样。策略模式使用接口占坑,然后传入实际对象调用需要的方法,而模板方法模式是用抽象方法占坑,粒度其实小一些。

晚绑定最典型的应用就是模板方法模式:抽象类确定基本的算法骨架,把不确定的、变化的部分做成抽象方法剥离出去,由子类来实现。

还是以发送验证码为例:

/*** 验证码发送器** @author mx*/
public abstract class AbstractValidateCodeSender {/*** 生成并发送验证码*/public void sendValidateCode() {// 1.生成验证码String code = generateValidateCode();// 2.把验证码存入Session// ....// 3.抽象方法占坑,用于发送验证码sendCode();}/*** 具体发送逻辑,留给子类实现:发送邮件、或发送短信都行*/protected abstract void sendCode();/*** 生成验证码** @return*/public String generateValidateCode() {return "123456";}}

对于上面的模板,我们可以有多种实现方式,以便把sendCode()这个坑填上:

/*** 短信验证码发送** @author mx*/
public class SmsValidateCodeSender extends AbstractValidateCodeSender {@Overrideprotected void sendCode() {// 通过阿里云短信发送}
}
/*** QQ邮箱验证码发送** @author mx*/
public class EmailValidateCodeSender extends AbstractValidateCodeSender {@Overrideprotected void sendCode() {// 通过QQ邮箱发送}
}
作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

进群,大家一起学习,一起进步,一起对抗互联网寒冬

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

实测有效的 8 个顶级Android 数据恢复工具

由于我们现在生活在一个依赖数字数据的时代,当重要文件从我们的 Android 手机中消失时,这将是一场数字噩梦。如果您没有预先备份Android手机上的数据或未能通过备份找到已删除的数据,那么选择最好的Android数据恢复软件是最佳选择。 因此&am…

ground truth 在深度学习任务中代表的是什么意思?

1、概念 在深度学习领域,ground truth (中文意思是“地面真实值”或“基准真实值”,简单理解就是真实值) 是指用于训练和评估模型的准确标签或数据。它是机器学习算法的参考标准,用于衡量模型的性的和判断模型的准确性,本文将介绍…

Postgresql数据库运维统计信息

如果需要使用以下运维信息,需要如下几步 修改postgresql.conf文件 #shared_preload_libraries # (change requires restart)shared_preload_libraries pg_stat_statements重启数据库创建扩展 CREATE EXTENSION IF NOT EXISTS pg_stat_statements;1. 统计信息…

AcWing 2816. 判断子序列

文章目录 AcWing 2816. 判断子序列我的思路CODE 欣赏大神代码给点思考 AcWing 2816. 判断子序列 题目链接:https://www.acwing.com/activity/content/problem/content/2981/ 我的思路 直接硬套模版,把两个指针两层循环写上如果匹配,记录数组…

苹果手机怎么卸载微信?记得掌握这两种方法!

微信是一款社交应用程序,在聊天过程中,我们会经常发送和接收各种形式的信息。随着时间的推移,微信缓存的文件会越来越多,占用的存储空间也会逐渐增加。 卸载微信可以释放手机内存,提高手机的运行速度。那么&#xff0…

Python 分解IP段获取所有IP(子网掩码)

需求 192.168.1.0/24,192.168.2.1-192.168.2.254,192.168.3.3 IP段格式已 "," 分割,获取所有IP 注意 1. 判断 IP 是否合规 2. 去除多余的字符,例如空格、换行符 3. 去重 代码 import re import ipaddressdef isIP(ip):p re.compile(^((…

基于若依的ruoyi-nbcio流程管理系统增加流程节点配置(二)

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码: https://gitee.com/nbacheng/ruoyi-nbcio 演示地址:RuoYi-Nbcio后台管理系统 上一节把数据库与相关基础数据字典准备好,下面就来实现相应的功能,目前先针对自定义…

问答社区运营的核心是什么?

问答社区是用户在平台上获得信息的一种方式,一般问答社区适用于医疗行业,法律行业等专业领域的行业,可以划分为知识型分享社区的一种,用户提供提问,邀请回答,选择最佳回复,设置问题围观&#xf…

《微信小程序开发从入门到实战》学习三十六

4.2 云开发JSON数据库 4.2.6 云开发JSON数据库 在集合对象上调用add方法可以在集和中可以插入一条记录,代码如下: db.collection(testOne).add({ // 在JSON数据库的testOne集合中增加一个记录 data:{ name: "write paper" }, // 插入数据成功…

力扣2.两数相加

题目描述 把题读懂后,这道题存在两个需要解决的问题:1.进位问题;2.两个链表长度不一 代码 class Solution {public ListNode addTwoNumbers(ListNode l1, ListNode l2) {//创建新链表的伪指针,指向链表的头结点ListNode prev n…

选择更灵活的设计工具:SOLIDWORKS 软件网络版与单机版的比较

随着科技的飞速发展,工程设计领域对于高效、灵活的设计工具需求日益增加。SOLIDWORKS 作为一款广受欢迎的三维设计软件,提供了网络版和单机版两种选择。在本文中,我们将深入探讨这两个版本的区别,并为您详细介绍它们的价格差异。 …

如何通过Portal实现消息集成

在数字化时代浪潮下,信息的流通与交互已变得至关重要,不论是在企业内部日常协作,还是与外部客户的紧密沟通,信息的快速、准确、实时传递都成为了确保业务顺畅进行的关键因素、决策精准的核心要素。 为了满足这种日益增长的需求&a…