learn.lianglianglee.com/专栏/左耳听风/034 编程范式游记(5)- 修饰器模式.md.html
2022-05-11 19:04:14 +08:00

1650 lines
49 KiB
HTML
Raw Permalink 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>034 编程范式游记5- 修饰器模式.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="/专栏/左耳听风/000 开篇词 洞悉技术的本质,享受科技的乐趣.md.html">000 开篇词 洞悉技术的本质,享受科技的乐趣.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/001 程序员如何用技术变现(上).md.html">001 程序员如何用技术变现(上).md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/002 程序员如何用技术变现(下).md.html">002 程序员如何用技术变现(下).md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/003 Equifax信息泄露始末.md.html">003 Equifax信息泄露始末.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/004 从Equifax信息泄露看数据安全.md.html">004 从Equifax信息泄露看数据安全.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/005 何为技术领导力.md.html">005 何为技术领导力.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/006 如何拥有技术领导力.md.html">006 如何拥有技术领导力.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/007 推荐阅读:每个程序员都该知道的事.md.html">007 推荐阅读:每个程序员都该知道的事.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/008 Go语言Docker和新技术.md.html">008 Go语言Docker和新技术.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/009 答疑解惑:渴望、热情和选择.md.html">009 答疑解惑:渴望、热情和选择.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/010 如何成为一个大家愿意追随的Leader.md.html">010 如何成为一个大家愿意追随的Leader.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/011 程序中的错误处理:错误返回码和异常捕捉.md.html">011 程序中的错误处理:错误返回码和异常捕捉.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/012 程序中的错误处理:异步编程和最佳实践.md.html">012 程序中的错误处理:异步编程和最佳实践.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/013 魔数 0x5f3759df.md.html">013 魔数 0x5f3759df.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/014 推荐阅读机器学习101.md.html">014 推荐阅读机器学习101.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/015 时间管理:同扭曲时间的事儿抗争.md.html">015 时间管理:同扭曲时间的事儿抗争.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/016 时间管理:投资赚取时间.md.html">016 时间管理:投资赚取时间.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/017 故障处理最佳实践:应对故障.md.html">017 故障处理最佳实践:应对故障.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/018 故障处理最佳实践:故障改进.md.html">018 故障处理最佳实践:故障改进.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/019 答疑解惑:我们应该能够识别的表象和本质.md.html">019 答疑解惑:我们应该能够识别的表象和本质.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/020 分布式系统架构的冰与火.md.html">020 分布式系统架构的冰与火.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/021 从亚马逊的实践,谈分布式系统的难点.md.html">021 从亚马逊的实践,谈分布式系统的难点.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/022 分布式系统的技术栈.md.html">022 分布式系统的技术栈.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/023 分布式系统关键技术:全栈监控.md.html">023 分布式系统关键技术:全栈监控.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/024 分布式系统关键技术:服务调度.md.html">024 分布式系统关键技术:服务调度.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/025 分布式系统关键技术:流量与数据调度.md.html">025 分布式系统关键技术:流量与数据调度.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/026 洞悉PaaS平台的本质.md.html">026 洞悉PaaS平台的本质.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/027 推荐阅读:分布式系统架构经典资料.md.html">027 推荐阅读:分布式系统架构经典资料.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/028 编程范式游记1- 起源.md.html">028 编程范式游记1- 起源.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/029 编程范式游记2- 泛型编程.md.html">029 编程范式游记2- 泛型编程.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/030 编程范式游记3 - 类型系统和泛型的本质.md.html">030 编程范式游记3 - 类型系统和泛型的本质.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/031 Git协同工作流你该怎样选.md.html">031 Git协同工作流你该怎样选.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/032 推荐阅读:分布式数据调度相关论文.md.html">032 推荐阅读:分布式数据调度相关论文.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/033 编程范式游记4- 函数式编程.md.html">033 编程范式游记4- 函数式编程.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/左耳听风/034 编程范式游记5- 修饰器模式.md.html">034 编程范式游记5- 修饰器模式.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/035 编程范式游记6- 面向对象编程.md.html">035 编程范式游记6- 面向对象编程.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/036 编程范式游记7- 基于原型的编程范式.md.html">036 编程范式游记7- 基于原型的编程范式.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/037 编程范式游记8- Go 语言的委托模式.md.html">037 编程范式游记8- Go 语言的委托模式.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/038 编程范式游记9- 编程的本质.md.html">038 编程范式游记9- 编程的本质.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/039 编程范式游记10- 逻辑编程范式.md.html">039 编程范式游记10- 逻辑编程范式.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/040 编程范式游记11- 程序世界里的编程范式.md.html">040 编程范式游记11- 程序世界里的编程范式.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/041 弹力设计篇之“认识故障和弹力设计”.md.html">041 弹力设计篇之“认识故障和弹力设计”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/042 弹力设计篇之“隔离设计”.md.html">042 弹力设计篇之“隔离设计”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/043 弹力设计篇之“异步通讯设计”.md.html">043 弹力设计篇之“异步通讯设计”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/044 弹力设计篇之“幂等性设计”.md.html">044 弹力设计篇之“幂等性设计”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/045 弹力设计篇之“服务的状态”.md.html">045 弹力设计篇之“服务的状态”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/046 弹力设计篇之“补偿事务”.md.html">046 弹力设计篇之“补偿事务”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/047 弹力设计篇之“重试设计”.md.html">047 弹力设计篇之“重试设计”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/048 弹力设计篇之“熔断设计”.md.html">048 弹力设计篇之“熔断设计”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/049 弹力设计篇之“限流设计”.md.html">049 弹力设计篇之“限流设计”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/050 弹力设计篇之“降级设计”.md.html">050 弹力设计篇之“降级设计”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/051 弹力设计篇之“弹力设计总结”.md.html">051 弹力设计篇之“弹力设计总结”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/052 区块链技术 - 区块链的革命性及技术概要.md.html">052 区块链技术 - 区块链的革命性及技术概要.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/053 区块链技术 - 区块链技术细节 - 哈希算法.md.html">053 区块链技术 - 区块链技术细节 - 哈希算法.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/054 区块链技术 - 区块链技术细节 - 加密和挖矿.md.html">054 区块链技术 - 区块链技术细节 - 加密和挖矿.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/055 区块链技术 - 去中心化的共识机制.md.html">055 区块链技术 - 去中心化的共识机制.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/056 区块链技术 - 智能合约.md.html">056 区块链技术 - 智能合约.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/057 区块链技术 - 传统金融和虚拟货币.md.html">057 区块链技术 - 传统金融和虚拟货币.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/058 管理设计篇之分布式锁.md.html">058 管理设计篇之分布式锁.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/059 管理设计篇之配置中心.md.html">059 管理设计篇之配置中心.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/060 管理设计篇之边车模式.md.html">060 管理设计篇之边车模式.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/061 管理设计篇之服务网格.md.html">061 管理设计篇之服务网格.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/062 管理设计篇之网关模式.md.html">062 管理设计篇之网关模式.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/063 管理设计篇之部署升级策略.md.html">063 管理设计篇之部署升级策略.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/064 性能设计篇之缓存.md.html">064 性能设计篇之缓存.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/065 性能设计篇之异步处理.md.html">065 性能设计篇之异步处理.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/066 性能设计篇之数据库扩展.md.html">066 性能设计篇之数据库扩展.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/067 性能设计篇之秒杀.md.html">067 性能设计篇之秒杀.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/068 性能设计篇之边缘计算.md.html">068 性能设计篇之边缘计算.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/069 程序员练级攻略2018开篇词.md.html">069 程序员练级攻略2018开篇词.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/070 程序员练级攻略2018零基础启蒙.md.html">070 程序员练级攻略2018零基础启蒙.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/071 程序员练级攻略2018正式入门.md.html">071 程序员练级攻略2018正式入门.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/072 程序员练级攻略2018程序员修养.md.html">072 程序员练级攻略2018程序员修养.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/073 程序员练级攻略2018编程语言.md.html">073 程序员练级攻略2018编程语言.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/074 程序员练级攻略:理论学科.md.html">074 程序员练级攻略:理论学科.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/075 程序员练级攻略2018系统知识.md.html">075 程序员练级攻略2018系统知识.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/076 程序员练级攻略2018软件设计.md.html">076 程序员练级攻略2018软件设计.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/077 程序员练级攻略2018Linux系统、内存和网络.md.html">077 程序员练级攻略2018Linux系统、内存和网络.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/078 程序员练级攻略2018异步IO模型和Lock-Free编程.md.html">078 程序员练级攻略2018异步IO模型和Lock-Free编程.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/079 程序员练级攻略2018Java底层知识.md.html">079 程序员练级攻略2018Java底层知识.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/080 程序员练级攻略2018数据库.md.html">080 程序员练级攻略2018数据库.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/081 程序员练级攻略2018分布式架构入门.md.html">081 程序员练级攻略2018分布式架构入门.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/082 程序员练级攻略2018分布式架构经典图书和论文.md.html">082 程序员练级攻略2018分布式架构经典图书和论文.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/083 程序员练级攻略2018分布式架构工程设计.md.html">083 程序员练级攻略2018分布式架构工程设计.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/084 程序员练级攻略2018微服务.md.html">084 程序员练级攻略2018微服务.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/085 程序员练级攻略2018容器化和自动化运维.md.html">085 程序员练级攻略2018容器化和自动化运维.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/086 程序员练级攻略2018机器学习和人工智能.md.html">086 程序员练级攻略2018机器学习和人工智能.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/087 程序员练级攻略2018前端基础和底层原理.md.html">087 程序员练级攻略2018前端基础和底层原理.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/088 程序员练级攻略2018前端性能优化和框架.md.html">088 程序员练级攻略2018前端性能优化和框架.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/089 程序员练级攻略2018UIUX设计.md.html">089 程序员练级攻略2018UIUX设计.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/090 程序员练级攻略2018技术资源集散地.md.html">090 程序员练级攻略2018技术资源集散地.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/091 程序员面试攻略:面试前的准备.md.html">091 程序员面试攻略:面试前的准备.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/092 程序员面试攻略:面试中的技巧.md.html">092 程序员面试攻略:面试中的技巧.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/093 程序员面试攻略:面试风格.md.html">093 程序员面试攻略:面试风格.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/094 程序员面试攻略:实力才是王中王.md.html">094 程序员面试攻略:实力才是王中王.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/095 高效学习:端正学习态度.md.html">095 高效学习:端正学习态度.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/096 高效学习:源头、原理和知识地图.md.html">096 高效学习:源头、原理和知识地图.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/097 高效学习:深度,归纳和坚持实践.md.html">097 高效学习:深度,归纳和坚持实践.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/098 高效学习:如何学习和阅读代码.md.html">098 高效学习:如何学习和阅读代码.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/099 高效学习:面对枯燥和量大的知识.md.html">099 高效学习:面对枯燥和量大的知识.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/100 高效沟通Talk和Code同等重要.md.html">100 高效沟通Talk和Code同等重要.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/101 高效沟通:沟通阻碍和应对方法.md.html">101 高效沟通:沟通阻碍和应对方法.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/102 高效沟通:沟通方式及技巧.md.html">102 高效沟通:沟通方式及技巧.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/103 高效沟通:沟通技术.md.html">103 高效沟通:沟通技术.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/104 高效沟通:好老板要善于提问.md.html">104 高效沟通:好老板要善于提问.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/105 高效沟通:好好说话的艺术.md.html">105 高效沟通:好好说话的艺术.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/106 加餐 谈谈我的“三观”.md.html">106 加餐 谈谈我的“三观”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/107 结束语 业精于勤,行成于思.md.html">107 结束语 业精于勤,行成于思.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>034 编程范式游记5- 修饰器模式</h1>
<p>在上一篇文章中我们领略了函数式编程的趣味和魅力主要讲了函数式编程的主要技术。还记得有哪些吗递归、Map、Reduce、Filter 等,并利用 Python 的 Decorator 和 Generator 功能,将多个函数组合成了管道。</p>
<p>此时,你心中可能会有个疑问,这个 decorator 又是怎样工作的呢这就是本文中要讲述的内容“Decorator 模式”,又叫“修饰器模式”,或是“装饰器模式”。</p>
<h1>Python 的 Decorator</h1>
<p>Python 的 Decorator 在使用上和 Java 的 Annotation以及 C# 的 Attribute很相似就是在方法名前面加一个 @XXX 注解来为这个方法装饰一些东西。但是Java/C# 的 Annotation 也很让人望而却步,太过于复杂了。你要玩它,需要先了解一堆 Annotation 的类库文档,感觉几乎就是在学另外一门语言。</p>
<p>而 Python 使用了一种相对于 Decorator Pattern 和 Annotation 来说非常优雅的方法,这种方法不需要你去掌握什么复杂的 OO 模型或是 Annotation 的各种类库规定,完全就是语言层面的玩法:一种函数式编程的技巧。</p>
<p>这是我最喜欢的一个模式了,也是一个挺好玩儿的东西,这个模式动用了函数式编程的一个技术——用一个函数来构造另一个函数。</p>
<p>好了,我们先来点感性认识,看一个 Python 修饰器的 Hello World 代码。</p>
<pre><code>def hello(fn):
def wrapper():
print &quot;hello, %s&quot; % fn.__name__
fn()
print &quot;goodbye, %s&quot; % fn.__name__
return wrapper
@hello
def Hao():
print &quot;i am Hao Chen&quot;
Hao()
</code></pre>
<p>代码的执行结果如下:</p>
<pre><code>$ python hello.py
hello, Hao
i am Hao Chen
goodbye, Hao
</code></pre>
<p>你可以看到如下的东西:</p>
<ol>
<li>函数 <code>Hao</code> 前面有个 @hello 的“注解”,<code>hello</code> 就是我们前面定义的函数 <code>hello</code></li>
<li><code>hello</code> 函数中,其需要一个 <code>fn</code> 的参数(这就是用来做回调的函数);</li>
<li>hello 函数中返回了一个 inner 函数 <code>wrapper</code>,这个 <code>wrapper</code>函数回调了传进来的 <code>fn</code>,并在回调前后加了两条语句。</li>
</ol>
<p>对于 Python 的这个 @注解语法糖syntactic sugar来说当你在用某个 @decorator 来修饰某个函数 <code>func</code> 时,如下所示:</p>
<pre><code>@decorator
def func():
pass
</code></pre>
<p>其解释器会解释成下面这样的语句:</p>
<pre><code>func = decorator(func)
</code></pre>
<p>嘿!这不就是把一个函数当参数传到另一个函数中,然后再回调吗?是的。但是,我们需要注意,那里还有一个赋值语句,把 decorator 这个函数的返回值赋值回了原来的 <code>func</code></p>
<p>我们再来看一个带参数的玩法:</p>
<pre><code>def makeHtmlTag(tag, *args, **kwds):
def real_decorator(fn):
css_class = &quot; class='{0}'&quot;.format(kwds[&quot;css_class&quot;]) \
if &quot;css_class&quot; in kwds else &quot;&quot;
def wrapped(*args, **kwds):
return &quot;&lt;&quot;+tag+css_class+&quot;&gt;&quot; + fn(*args, **kwds) + &quot;&lt;/&quot;+tag+&quot;&gt;&quot;
return wrapped
return real_decorator
@makeHtmlTag(tag=&quot;b&quot;, css_class=&quot;bold_css&quot;)
@makeHtmlTag(tag=&quot;i&quot;, css_class=&quot;italic_css&quot;)
def hello():
return &quot;hello world&quot;
print hello()
# 输出:
# &lt;b class='bold_css'&gt;&lt;i class='italic_css'&gt;hello world&lt;/i&gt;&lt;/b&gt;
</code></pre>
<p>在上面这个例子中,我们可以看到:<code>makeHtmlTag</code>有两个参数。所以,为了让 <code>hello = makeHtmlTag(arg1, arg2)(hello)</code> 成功, <code>makeHtmlTag</code> 必需返回一个 decorator这就是为什么我们在 <code>makeHtmlTag</code> 中加入了 <code>real_decorator()</code>)。</p>
<p>这样一来,我们就可以进入到 decorator 的逻辑中去了——decorator 得返回一个 wrapperwrapper 里回调 <code>hello</code>。看似那个 <code>makeHtmlTag()</code> 写得层层叠叠,但是,已经了解了本质的我们觉得写得很自然。</p>
<p>我们再来看一个为其它函数加缓存的示例:</p>
<pre><code>from functools import wraps
def memoization(fn):
cache = {}
miss = object()
@wraps(fn)
def wrapper(*args):
result = cache.get(args, miss)
if result is miss:
result = fn(*args)
cache[args] = result
return result
return wrapper
@memoization
def fib(n):
if n &lt; 2:
return n
return fib(n - 1) + fib(n - 2)
</code></pre>
<p>上面这个例子中,是一个斐波那契数例的递归算法。我们知道,这个递归是相当没有效率的,因为会重复调用。比如:我们要计算 fib(5),于是其分解成 <code>fib(4) + fib(3)</code>,而 <code>fib(4)</code> 分解成 <code>fib(3) + fib(2)</code><code>fib(3)</code> 又分解成<code>fib(2) + fib(1)</code>……你可看到,基本上来说,<code>fib(3)</code>, <code>fib(2)</code>, <code>fib(1)</code>在整个递归过程中被调用了至少两次。</p>
<p>而我们用 decorator在调用函数前查询一下缓存如果没有才调用有了就从缓存中返回值。一下子这个递归从二叉树式的递归成了线性的递归。<code>wraps</code> 的作用是保证 <code>fib</code> 的函数名不被 <code>wrapper</code> 所取代。</p>
<p>除此之外Python 还支持类方式的 decorator。</p>
<pre><code>class myDecorator(object):
def __init__(self, fn):
print &quot;inside myDecorator.__init__()&quot;
self.fn = fn
def __call__(self):
self.fn()
print &quot;inside myDecorator.__call__()&quot;
@myDecorator
def aFunction():
print &quot;inside aFunction()&quot;
print &quot;Finished decorating aFunction()&quot;
aFunction()
# 输出:
# inside myDecorator.__init__()
# Finished decorating aFunction()
# inside aFunction()
# inside myDecorator.__call__()
</code></pre>
<p>上面这个示例展示了,用类的方式声明一个 decorator。我们可以看到这个类中有两个成员</p>
<ol>
<li>一个是<code>__init__()</code>,这个方法是在我们给某个函数 decorate 时被调用,所以,需要有一个 <code>fn</code> 的参数,也就是被 decorate 的函数。</li>
<li>一个是<code>__call__()</code>,这个方法是在我们调用被 decorate 的函数时被调用的。</li>
</ol>
<p>从上面的输出中,可以看到整个程序的执行顺序。这看上去要比“函数式”的方式更易读一些。</p>
<p>我们来看一个实际点的例子。下面这个示例展示了通过 URL 的路由来调用相关注册的函数示例:</p>
<pre><code>class MyApp():
def __init__(self):
self.func_map = {}
def register(self, name):
def func_wrapper(func):
self.func_map[name] = func
return func
return func_wrapper
def call_method(self, name=None):
func = self.func_map.get(name, None)
if func is None:
raise Exception(&quot;No function registered against - &quot; + str(name))
return func()
app = MyApp()
@app.register('/')
def main_page_func():
return &quot;This is the main page.&quot;
@app.register('/next_page')
def next_page_func():
return &quot;This is the next page.&quot;
print app.call_method('/')
print app.call_method('/next_page')
</code></pre>
<p>注意:上面这个示例中 decorator 类不是真正的 decorator其中也没有<code>__call__()</code>并且wrapper 返回了原函数。所以,原函数没有发生任何变化。</p>
<h1>Go 语言的 Decorator</h1>
<p>Python 有语法糖,所以写出来的代码比较酷。但是对于没有修饰器语法糖这类语言,写出来的代码会是怎么样的?我们来看一下 Go 语言的代码。</p>
<p>还是从一个 Hello World 开始。</p>
<pre><code>package main
import &quot;fmt&quot;
func decorator(f func(s string)) func(s string) {
return func(s string) {
fmt.Println(&quot;Started&quot;)
f(s)
fmt.Println(&quot;Done&quot;)
}
}
func Hello(s string) {
fmt.Println(s)
}
func main() {
decorator(Hello)(&quot;Hello, World!&quot;)
}
</code></pre>
<p>可以看到,我们动用了一个高阶函数 <code>decorator()</code>,在调用的时候,先把 <code>Hello()</code> 函数传进去,然后其返回一个匿名函数。这个匿名函数中除了运行了自己的代码,也调用了被传入的 <code>Hello()</code> 函数。</p>
<p>这个玩法和 Python 的异曲同工只不过Go 并不支持像 Python 那样的 @decorator 语法糖。所以,在调用上有些难看。当然,如果要想让代码容易读一些,你可以这样:</p>
<pre><code>hello := decorator(Hello)
hello(&quot;Hello&quot;)
</code></pre>
<p>我们再来看一个为函数 log 消耗时间的例子:</p>
<pre><code>type SumFunc func(int64, int64) int64
func getFunctionName(i interface{}) string {
return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
}
func timedSumFunc(f SumFunc) SumFunc {
return func(start, end int64) int64 {
defer func(t time.Time) {
fmt.Printf(&quot;--- Time Elapsed (%s): %v ---\n&quot;,
getFunctionName(f), time.Since(t))
}(time.Now())
return f(start, end)
}
}
func Sum1(start, end int64) int64 {
var sum int64
sum = 0
if start &gt; end {
start, end = end, start
}
for i := start; i &lt;= end; i++ {
sum += i
}
return sum
}
func Sum2(start, end int64) int64 {
if start &gt; end {
start, end = end, start
}
return (end - start + 1) * (end + start) / 2
}
func main() {
sum1 := timedSumFunc(Sum1)
sum2 := timedSumFunc(Sum2)
fmt.Printf(&quot;%d, %d\n&quot;, sum1(-10000, 10000000), sum2(-10000, 10000000))
}
</code></pre>
<p>关于上面的代码:</p>
<ul>
<li>有两个 Sum 函数,<code>Sum1()</code> 函数就是简单地做个循环,<code>Sum2()</code> 函数动用了数据公式。(注意:<code>start</code><code>end</code> 有可能有负数的情况。)</li>
<li>代码中使用了 Go 语言的反射机制来获取函数名。</li>
<li>修饰器函数是 <code>timedSumFunc()</code></li>
</ul>
<p>再来看一个 HTTP 路由的例子:</p>
<pre><code>func WithServerHeader(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Println(&quot;---&gt;WithServerHeader()&quot;)
w.Header().Set(&quot;Server&quot;, &quot;HelloServer v0.0.1&quot;)
h(w, r)
}
}
func WithAuthCookie(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Println(&quot;---&gt;WithAuthCookie()&quot;)
cookie := &amp;http.Cookie{Name: &quot;Auth&quot;, Value: &quot;Pass&quot;, Path: &quot;/&quot;}
http.SetCookie(w, cookie)
h(w, r)
}
}
func WithBasicAuth(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Println(&quot;---&gt;WithBasicAuth()&quot;)
cookie, err := r.Cookie(&quot;Auth&quot;)
if err != nil || cookie.Value != &quot;Pass&quot; {
w.WriteHeader(http.StatusForbidden)
return
}
h(w, r)
}
}
func WithDebugLog(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Println(&quot;---&gt;WithDebugLog&quot;)
r.ParseForm()
log.Println(r.Form)
log.Println(&quot;path&quot;, r.URL.Path)
log.Println(&quot;scheme&quot;, r.URL.Scheme)
log.Println(r.Form[&quot;url_long&quot;])
for k, v := range r.Form {
log.Println(&quot;key:&quot;, k)
log.Println(&quot;val:&quot;, strings.Join(v, &quot;&quot;))
}
h(w, r)
}
}
func hello(w http.ResponseWriter, r *http.Request) {
log.Printf(&quot;Received Request %s from %s\n&quot;, r.URL.Path, r.RemoteAddr)
fmt.Fprintf(w, &quot;Hello, World! &quot;+r.URL.Path)
}
</code></pre>
<p>上面的代码中,我们写了多个函数。有写 HTTP 响应头的,有写认证 Cookie 的,有检查认证 Cookie 的,有打日志的……在使用过程中,我们可以把其嵌套起来使用,在修饰过的函数上继续修饰,这样就可以拼装出更复杂的功能。</p>
<pre><code>func main() {
http.HandleFunc(&quot;/v1/hello&quot;, WithServerHeader(WithAuthCookie(hello)))
http.HandleFunc(&quot;/v2/hello&quot;, WithServerHeader(WithBasicAuth(hello)))
http.HandleFunc(&quot;/v3/hello&quot;, WithServerHeader(WithBasicAuth(WithDebugLog(hello))))
err := http.ListenAndServe(&quot;:8080&quot;, nil)
if err != nil {
log.Fatal(&quot;ListenAndServe: &quot;, err)
}
}
</code></pre>
<p>当然,如果一层套一层不好看的话,我们可以使用 pipeline 的玩法——我们需要先写一个工具函数——用来遍历并调用各个 decorator</p>
<pre><code>type HttpHandlerDecorator func(http.HandlerFunc) http.HandlerFunc
func Handler(h http.HandlerFunc, decors ...HttpHandlerDecorator) http.HandlerFunc {
for i := range decors {
d := decors[len(decors)-1-i] // iterate in reverse
h = d(h)
}
return h
}
</code></pre>
<p>然后,我们就可以像下面这样使用了。</p>
<pre><code>http.HandleFunc(&quot;/v4/hello&quot;, Handler(hello,
WithServerHeader, WithBasicAuth, WithDebugLog))
</code></pre>
<p>这样的代码是不是更易读了一些pipeline 的功能也就出来了。</p>
<p>不过,对于 Go 的修饰器模式,还有一个小问题——好像无法做到泛型,就像上面那个计算时间的函数一样,其代码耦合了需要被修饰的函数的接口类型,无法做到非常通用。如果这个事解决不了,那么,这个修饰器模式还是有点不好用的。</p>
<p>因为 Go 语言不像 Python 和 JavaPython 是动态语言,而 Java 有语言虚拟机,所以它们可以干好些比较变态的事,然而 Go 语言是一个静态的语言这意味着其类型需要在编译时就要搞定否则无法编译。不过Go 语言支持的最大的泛型是 interface{},还有比较简单的 reflection 机制,在上面做做文章,应该还是可以搞定的。</p>
<p>废话不说,下面是我用 reflection 机制写的一个比较通用的修饰器(为了便于阅读,我删除了出错判断代码)。</p>
<pre><code>func Decorator(decoPtr, fn interface{}) (err error) {
var decoratedFunc, targetFunc reflect.Value
decoratedFunc = reflect.ValueOf(decoPtr).Elem()
targetFunc = reflect.ValueOf(fn)
v := reflect.MakeFunc(targetFunc.Type(),
func(in []reflect.Value) (out []reflect.Value) {
fmt.Println(&quot;before&quot;)
out = targetFunc.Call(in)
fmt.Println(&quot;after&quot;)
return
})
decoratedFunc.Set(v)
return
}
</code></pre>
<p>上面的代码动用了 <code>reflect.MakeFunc()</code> 函数制作出了一个新的函数。其中的 <code>targetFunc.Call(in)</code> 调用了被修饰的函数。关于 Go 语言的反射机制,推荐官方文章——《<a href="https://blog.golang.org/laws-of-reflection">The Laws of Reflection</a>》,在这里我不多说了。</p>
<p>上面这个 <code>Decorator()</code> 需要两个参数:</p>
<ul>
<li>第一个是出参 <code>decoPtr</code> ,就是完成修饰后的函数。</li>
<li>第二个是入参 <code>fn</code> ,就是需要修饰的函数。</li>
</ul>
<p>这样写是不是有些二?的确是的。不过,这是我个人在 Go 语言里所能写出来的最好的代码了。如果你知道更优雅的写法,请你一定告诉我!</p>
<p>好的,让我们来看一下使用效果。首先,假设我们有两个需要修饰的函数:</p>
<pre><code>func foo(a, b, c int) int {
fmt.Printf(&quot;%d, %d, %d \n&quot;, a, b, c)
return a + b + c
}
func bar(a, b string) string {
fmt.Printf(&quot;%s, %s \n&quot;, a, b)
return a + b
}
</code></pre>
<p>然后,我们可以这样做:</p>
<pre><code>type MyFoo func(int, int, int) int
var myfoo MyFoo
Decorator(&amp;myfoo, foo)
myfoo(1, 2, 3)
</code></pre>
<p>你会发现,使用 <code>Decorator()</code> 时,还需要先声明一个函数签名,感觉好傻啊。一点都不泛型,不是吗?谁叫这是有类型的静态编译的语言呢?</p>
<p>嗯。如果你不想声明函数签名,那么也可以这样:</p>
<pre><code>mybar := bar
Decorator(&amp;mybar, bar)
mybar(&quot;hello,&quot;, &quot;world!&quot;)
</code></pre>
<p>好吧,看上去不是那么的漂亮,但是 it works。看样子 Go 语言目前本身的特性无法做成像 Java 或 Python 那样,对此,我们只能多求 Go 语言多放糖了!</p>
<h1>小结</h1>
<p>好了,讲了那么多的例子,看了那么多的代码,我估计你可能有点晕,让我们来做个小结吧。</p>
<p>通过上面 Python 和 Go 修饰器的例子,我们可以看到,所谓的修饰器模式其实是在做下面的几件事。</p>
<ul>
<li>表面上看,修饰器模式就是扩展现有的一个函数的功能,让它可以干一些其他的事,或是在现有的函数功能上再附加上一些别的功能。</li>
<li>除了我们可以感受到<strong>函数式编程</strong>下的代码扩展能力,我们还能感受到函数的互相和随意拼装带来的好处。</li>
<li>但是深入一下我们不难发现Decorator 这个函数其实是可以修饰几乎所有的函数的。于是,这种可以通用于其它函数的编程方式,可以很容易地将一些非业务功能的、属于控制类型的代码给抽象出来(所谓的控制类型的代码就是像 for-loop或是打日志或是函数路由或是求函数运行时间之类的非业务功能性的代码</li>
</ul>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/左耳听风/033 编程范式游记4- 函数式编程.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/左耳听风/035 编程范式游记6- 面向对象编程.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":"709978186d303cfa","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>