learn.lianglianglee.com/专栏/白话设计模式 28 讲(完)/24 深入解读回调机制:把你技能亮出来.md.html
2022-05-11 19:04:14 +08:00

833 lines
25 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>24 深入解读回调机制:把你技能亮出来.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/白话设计模式 28 讲(完)/00 生活中的设计模式:启程之前,请不要错过我.md.html">00 生活中的设计模式:启程之前,请不要错过我.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/01 监听模式:坑爹的热水器.md.html">01 监听模式:坑爹的热水器.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/02 适配模式:身高不够鞋来凑.md.html">02 适配模式:身高不够鞋来凑.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/03 状态模式:人与水的三态.md.html">03 状态模式:人与水的三态.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/04 单例模式:你是我生命的唯一.md.html">04 单例模式:你是我生命的唯一.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/05 职责模式:我的假条去哪了.md.html">05 职责模式:我的假条去哪了.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/06 中介模式:找房子问中介.md.html">06 中介模式:找房子问中介.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/07 代理模式:帮我拿一下快递.md.html">07 代理模式:帮我拿一下快递.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/08 装饰模式:你想怎么穿就怎么穿.md.html">08 装饰模式:你想怎么穿就怎么穿.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/09 工厂模式:你要拿铁还是摩卡.md.html">09 工厂模式:你要拿铁还是摩卡.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/10 迭代模式:下一个就是你了.md.html">10 迭代模式:下一个就是你了.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/11 组合模式:自己组装电脑.md.html">11 组合模式:自己组装电脑.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/12 构建模式:想要车还是庄园.md.html">12 构建模式:想要车还是庄园.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/13 克隆模式:给你一个分身术.md.html">13 克隆模式:给你一个分身术.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/14 策略模式:怎么来不重要,人到就行.md.html">14 策略模式:怎么来不重要,人到就行.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/15 命令模式:大闸蟹,走起!.md.html">15 命令模式:大闸蟹,走起!.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/16 备忘模式:好记性不如烂笔头.md.html">16 备忘模式:好记性不如烂笔头.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/17 享元模式:颜料很贵必须充分利用.md.html">17 享元模式:颜料很贵必须充分利用.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/18 外观模式:学妹别慌,学长帮你.md.html">18 外观模式:学妹别慌,学长帮你.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/19 访问模式:一千个读者一千个哈姆雷特.md.html">19 访问模式:一千个读者一千个哈姆雷特.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/20 生活中的设计模式:与经典设计模式的不解渊源.md.html">20 生活中的设计模式:与经典设计模式的不解渊源.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/21 生活中的设计模式:那些未完待续的设计模式.md.html">21 生活中的设计模式:那些未完待续的设计模式.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/22 深入解读过滤器模式:制作一杯鲜纯细腻的豆浆.md.html">22 深入解读过滤器模式:制作一杯鲜纯细腻的豆浆.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/23 深入解读对象池技术:共享让生活更便捷.md.html">23 深入解读对象池技术:共享让生活更便捷.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/白话设计模式 28 讲(完)/24 深入解读回调机制:把你技能亮出来.md.html">24 深入解读回调机制:把你技能亮出来.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/25 谈谈我对设计模式的理解.md.html">25 谈谈我对设计模式的理解.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/26 谈谈我对设计原则的思考.md.html">26 谈谈我对设计原则的思考.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/27 谈谈我对项目重构的看法.md.html">27 谈谈我对项目重构的看法.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>24 深入解读回调机制:把你技能亮出来</h1>
<blockquote>
<p>铁打的公司流水的员工职场中经常有新的员工来也有老的员工走。为迎接新员工的到来Tony 所在的公司每个月都有一个新人见面会,在见面会上每个新人都要给大家表演一个节目,节目类型不限,内容随意!只要<strong>把你的技能都亮出来</strong>,把最有趣的一面展示给大家就行。有的人选择唱一首歌,有的人拉一曲 Ukulele有的人会说一搞笑段子有的人会表演魔术还有的人耍起了滑板真是各种鬼才……</p>
</blockquote>
<p><img src="assets/a9827b90-9624-11e8-9c35-b59aad3fef8b.jpg" alt="img" /></p>
<h3>用程序来模拟生活</h3>
<p>职场处处艰辛,但生活充满乐趣!每个人有自己的爱好,每个人也有自己擅长的技能。在新人见面会上把自己最擅长的一面展示出来,是让大家快速记住你的最好方式。下面我们用程序来模拟一下这个场景。</p>
<p>源码示例:</p>
<pre><code class="language-python">class Employee:
&quot;&quot;&quot;公司员工&quot;&quot;&quot;
def __init__(self, name):
self.__name = name
def doPerformance(self, skill):
print(self.__name + &quot;的表演:&quot;, end=&quot;&quot;)
skill()
def sing():
&quot;&quot;&quot;唱歌&quot;&quot;&quot;
print(&quot;唱一首歌&quot;)
def dling():
&quot;&quot;&quot;拉Ukulele&quot;&quot;&quot;
print(&quot;拉一曲Ukulele&quot;)
def joke():
&quot;&quot;&quot;说段子&quot;&quot;&quot;
print(&quot;说一搞笑段子&quot;)
def performMagicTricks():
&quot;&quot;&quot;表演魔术&quot;&quot;&quot;
print(&quot;神秘魔术&quot;)
def skateboarding():
&quot;&quot;&quot;玩滑板&quot;&quot;&quot;
print(&quot;酷炫滑板&quot;)
</code></pre>
<p>测试代码:</p>
<pre><code class="language-python">def testSkill():
helen = Employee(&quot;Helen&quot;)
helen.doPerformance(sing)
frank = Employee(&quot;Frank&quot;)
frank.doPerformance(dling)
jacky = Employee(&quot;Jacky&quot;)
jacky.doPerformance(joke)
chork = Employee(&quot;Chork&quot;)
chork.doPerformance(performMagicTricks)
Kerry = Employee(&quot;Kerry&quot;)
Kerry.doPerformance(skateboarding)
</code></pre>
<p>输出结果:</p>
<pre><code>Helen的表演:唱一首歌
Frank的表演:拉一曲Ukulele
Jacky的表演:说一搞笑段子
Chork的表演:神秘魔术
Kerry的表演:酷炫滑板
</code></pre>
<h3>从剧情中思考回调机制</h3>
<p>在上面的示例中,每一个新员工都要进行表演,每个人表演自己擅长的技能。因此我们定义了一个 Employee 类,里面有一个 doPerformance 方法,用来进行表演节目;但每个人擅长的技能都不一样,因此我们为每一个上台表演的人定义了一个方法,在调用时传递给 doPerformance。像这样将一个函数传递给另一个函数的方式叫<strong>回调机制</strong></p>
<h4>回调机制</h4>
<blockquote>
<p>把函数作为参数,传递给另一个函数,延迟到另一个函数的某个时刻执行的过程叫<strong>回调</strong>。假设有一个函数叫 callback(args); ,这个函数可以作为参数传递给另一个函数 otherFun(fun, args); ,如 otherFun(callback, [1, 2, 3])。那么 callback 叫回调函数otherFun 叫高阶函数,也叫包含(调用)函数。</p>
<p>回调函数的本质是一种模式(一种解决常见问题的模式),或说一种机制;因此我们把回调函数的实现方式也被称为回调模式或回调机制。</p>
</blockquote>
<p>在上面示例中doPerformance 就是一个高阶函数(包含函数),为每一个表演者定义的方法(如 sing、dling、joke就是回调函数。</p>
<h4>设计思想</h4>
<p>回调函数来自一种著名的编程范式——函数式编程,在函数式编程中可以指定函数作为参数。函数是 Python 内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计,也称为函数式编程。把函数作为参数传入给另一个函数的回调机制是函数式编程的核心思想。</p>
<p>我们在程序开发中经常会用到一些库,如 Python 内置的库,第三方库;这些库会定义一些通用的方法(如 filter()、map()),这些都是高阶函数。我们在调用的时候要先定义一个回调函数以实现特定的功能,并将这个函数作为参数传递给高阶函数。其过程如下图:</p>
<p><img src="assets/1d91acd0-9626-11e8-bd60-15398afc36e1.jpg" alt="img" /></p>
<p>当我们作为参数传递一个回调函数给另一个函数时,只传递了这个函数的定义,并没有在参数中执行它,而是在包含函数函数体内的某个位置被执行,就像回调函数在包含函数的函数体内定义一样。</p>
<p><strong>【应用实例】</strong></p>
<p>如我们要求一个整数数组 [2, 3, 6, 9, 12, 15, 18] 中所有的偶数和大于 10 的数,可以这样实现:</p>
<pre><code class="language-python">def isEvenNumber(num):
return num % 2 == 0
def isGreaterThanTen(num):
return num &gt; 10
def getEvenNumbers(fun, elements):
newList = []
for item in elements:
if (fun(item)):
newList.append(item)
return newList
def testCallback():
elements = [2, 3, 6, 9, 12, 15, 18]
list1 = getEvenNumbers(isEvenNumber, elements)
list2 = getEvenNumbers(isGreaterThanTen, elements)
print(&quot;所有的偶数:&quot;, list1)
print(&quot;大于10的数&quot;, list2)
</code></pre>
<p>结果如下:</p>
<pre><code>所有的偶数: [2, 6, 12, 18]
大于10的数 [12, 15, 18]
</code></pre>
<p>上面这个例子,我们只是演示一下回调函数如何实现。在真正的项目中,可直接使用 Python 内置的 filter 函数和 lambda 表达式,代码更简洁,如下:</p>
<pre><code class="language-python">list1 = list(filter(lambda x: x % 2 == 0, elements))
list2 = list(filter(lambda x: x &gt; 10, elements))
</code></pre>
<h3>面向对象的方式实现回调机制</h3>
<p>回调函数属于函数式编程,也就是面向过程式的编程。在面向对象的编程中,如何实现这种机制呢?特别是对于不支持函数作为参数来传递的语言(如 Java。回想一下学习过的这些设计模式也许你能找到。</p>
<p>对了,就是策略模式。我们知道策略模式是定义一系列算法,将每个算法都封装起来,并且使他们之间可以相互替换。</p>
<h3>实战应用</h3>
<p>下面,我们用策略模式来实现示例中的这种回调机制。</p>
<p><strong>源码示例:</strong></p>
<pre><code class="language-python">from abc import ABCMeta, abstractmethod
# 引入ABCMeta和abstractmethod来定义抽象类和抽象方法
class Skill(metaclass=ABCMeta):
&quot;&quot;&quot;技能的抽象类&quot;&quot;&quot;
@abstractmethod
def performance(self):
&quot;&quot;&quot;技能表演&quot;&quot;&quot;
pass
class NewEmployee:
&quot;&quot;&quot;公司新员工&quot;&quot;&quot;
def __init__(self, name):
self.__name = name
def doPerformance(self, skill):
print(self.__name + &quot;的表演:&quot;, end=&quot;&quot;)
skill.performance()
class Sing(Skill):
&quot;&quot;&quot;唱歌&quot;&quot;&quot;
def performance(self):
print(&quot;唱一首歌&quot;)
class Joke(Skill):
&quot;&quot;&quot;说段子&quot;&quot;&quot;
def performance(self):
print(&quot;说一搞笑段子&quot;)
class Dling(Skill):
&quot;&quot;&quot;拉Ukulele&quot;&quot;&quot;
def performance(self):
print(&quot;拉一曲Ukulele&quot;)
class PerformMagicTricks(Skill):
&quot;&quot;&quot;表演魔术&quot;&quot;&quot;
def performance(self):
print(&quot;神秘魔术&quot;)
class Skateboarding(Skill):
&quot;&quot;&quot;玩滑板&quot;&quot;&quot;
def performance(self):
print(&quot;酷炫滑板&quot;)
</code></pre>
<p><strong>测试代码:</strong></p>
<pre><code class="language-python">def testStrategySkill():
helen = NewEmployee(&quot;Helen&quot;)
helen.doPerformance(Sing())
frank = NewEmployee(&quot;Frank&quot;)
frank.doPerformance(Dling())
jacky = NewEmployee(&quot;Jacky&quot;)
jacky.doPerformance(Joke())
chork = NewEmployee(&quot;Chork&quot;)
chork.doPerformance(PerformMagicTricks())
Kerry = NewEmployee(&quot;Kerry&quot;)
Kerry.doPerformance(Skateboarding())
</code></pre>
<p>自己测试一下,结果和回调函数的方式是一样的。</p>
<p>这种用面向对象的方式实现的类图如下:</p>
<p><img src="assets/9ba733b0-9626-11e8-9f67-05ec09da262a.jpg" alt="enter image description here" /></p>
<p>有人可能会问上面这个类图和策略模式不太一样啊!策略模式中 Context 和 Strategy 是一种聚合关系,即 Context 中存有 Strategy 的对象;而这里 NewEmployee 和 Skill 是一个依赖关系NewEmployee 不存 Skill 的对象。这里要说明的设计模式不是一成不变的,是可以根据实现情况灵活变通的。如果你愿意,依然可以写成聚合关系,但代码将不会这么优雅。</p>
<p><strong>Java 的实现</strong></p>
<p>用 Java 这种支持匿名类的语言来实现,更能感受到回调的味道,代码也更简洁和优雅,如下:</p>
<pre><code class="language-java">/**
* 定义一个技能的接口
*/
interface ISkill {
public void performance();
}
/**
* 员工类
*/
public class NewEmployee {
private String name;
public NewEmployee(String name) {
this.name = name;
}
public void doPerformance(ISkill skill) {
System.out.print(this.name + &quot;的表演:&quot;);
skill.performance();
}
/**
* 用Main方法来测试
*/
public static void main(String args[])
{
NewEmployee helen = new NewEmployee(&quot;Helen&quot;);
helen.doPerformance(new ISkill() {
@Override
public void performance() {
System.out.println(&quot;说一搞笑段子&quot;);
}
});
NewEmployee frank = new NewEmployee(&quot;Frank&quot;);
frank.doPerformance(new ISkill() {
@Override
public void performance() {
System.out.println(&quot;拉一曲Ukulele&quot;);
}
});
}
}
</code></pre>
<h4>设计要点</h4>
<p>在设计回调机制的程序时要注意以下几点:</p>
<ul>
<li>在支持函数式编程的语言中,可以使用回调函数实现。作为参数传递的函数称为回调函数,接受回调函数(参数)的函数称为高阶函数或包含函数。</li>
<li>只支持面向对象编程的语言中,可以使用策略模式来实现回调机制。</li>
</ul>
<h4>回调机制的优点</h4>
<ul>
<li>避免重复代码</li>
<li>增强代码的可维护性</li>
<li>有更多定制的功能</li>
</ul>
<h4>回调机制的缺点</h4>
<p>可能出现“回调地狱”的问题,即多重的回调函数调用。如回调函数 A 被高阶函数 B 调用,同时 B 本身又是一个回调函数被函数 C 调用。我们应尽量避免这种多重调用的情况,否则代码可读性很差,程序将很难维护。</p>
<h3>应用场景</h3>
<ul>
<li>在第三方库和框架中。</li>
<li>异步执行(如读文件,发送 HTTP 请求)。</li>
<li>在你需要更多的通用功能的地方更好地实现抽象(可处理各种类型的函数)。</li>
</ul>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/白话设计模式 28 讲(完)/23 深入解读对象池技术:共享让生活更便捷.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/白话设计模式 28 讲(完)/25 谈谈我对设计模式的理解.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997bb1a8563cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>