假如你是一家视频网站的后端开发工程师,你们网站有以下几个版块:
在视频刚上线初期,为了吸引客户,你们采取了免费政策,所有视频免费观看,迅速吸引了一大批用户,免费一段时间后,发现每天巨大的带宽费用公司承受不起,所以准备对比较受欢迎的几个版块进行收费,其中包括“欧美”和“北京”专区,拿到这个需求后,想了想,想收费就需要对用户进行认证,认证通过后,再判断这个用户是否是VIP付费会员就可以了,是VIP就让他观看,不是VIP就不让他观看呗!你觉得这个需求很简单,因为要对多个版块进行认证,那应该把认证功能提取出来单独写个模块,然后每个版块里调用就可以了,于是你轻轻松松的实现了下面的功能:
代码执行结果:
此时你信心满满的把这个代码提交给你的TEAM LEADER审核,没成想,没过5分钟,代码就被打回来了, TEAM LEADER给你反馈是,我现在有很多模块需要加认证模块,你的代码虽然实现了功能,但是需要更改需要加认证的各个模块的代码,这直接违反了软件开发中的一个原则“开放-封闭”原则,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:
封闭:已实现功能的代码块不应该被修改
开放:对现有功能的扩展开放
这个原则你还是第一次听说,再次感受到了野生程序员和正规军的差距,可是,老大要求的实现要如何实现呢?如何在不该原有功能的情况下加上认证功能呢?你一时想不出来思路,只好带着这个问题回家继续写,媳妇不在家,去隔壁老王家串门了,你正好落得清静,一不小心就想到了解决方案,不改源代码可以实现啊。
你肯定知道什么是高阶函数,就是把一个函数当作一个参数传递给另一个函数,那么,我只需要写一个认证方法,每次调用需要验证的功能时,直接把这个函数当作一个参数传给这个验证模块不就行了吗?哈哈,机智如我,于是你啪啪啪改写了之前的代码:
你明白我想传达什么意思了么? 你:......好像不太明白。 老王:好吧,那我在给你点一下,你之前写的下面这段调用认证的代码
login(America)
login(Beijing)
你之所以修改了调用方式,是因为用户每次调用时需要执行login(America)类似的,其实只需要稍微改改就可以了:
America = login(America)
Beijing = login(Beijing)
这样你,其它人调用Beijing时,其实相当于调用了login(Beijing),通过login里的验证后,就会自动调用Beijing功能。 你:我擦,还真是唉。。。,老王,还是你nb。。。不过,等等,我这样写了好,那用户调用时,应该是下面这个样子:
America = login(America)
Beijing = login(Beijing)
America()
Beijing()
那么此时问题又来了,America = login(America)即先执行完右边的login(America)然后把结果赋值给了左边的America,即你还没有调用,程序自己就执行了,那么这个代码应该等我用户登陆的时候才执行对吧,不信我试给你看:
运行结果如下:
请输入你的姓名:
果然老王说的是对的,根本就没有去调用,这个代码就自动执行了,这时你说:这个问题应该怎么解决呢?老王问:你知道嵌套函数吗?你回答:知道。老王说:想实现一开始你写的america = login(america)不触发你函数的执行,只需要在这个login里面再定义一层函数,第一次调用america = login(america)只调用到外层login,这个login虽然会执行,但不会触发认证了,因为认证的所有代码被封装在login里层的新定义 的函数里了,login只返回 里层函数的函数名,这样下次再执行america()时, 就会调用里层函数啦,你说:...这是什么意思?我一脸懵逼。老王说:算了,还是给你看代码吧:
让我们来看一下代码的执行顺序:
1.程序直接到了最下边America = login(America),调用了login,并把函数当作参数传递进去;
2.在login函数内部,程序从上到下执行,首先执行inner(),在inner()函数里做了认证;
3.inner()函数执行完,返回inner函数,此时return inner即代表返回的是inner函数的内存地址;
4.在代码的底部,America = login(America),login(America)返回的是inner的内存地址;
5.我们刚刚说:如果打印的是函数名,则是函数的内存地址,如果函数名加上(),那么就是执行函数;
6.所以America = login(America)此时左边的America代表的是inner函数的内存地址,我们打印后可以看到;
7.America()执行innera函数;
此时问题暂时得到解决,在刚刚的代码中,有人会说,returni inner表示返回一个值代表着程序的终止,那么为什么我还能运行America()呢?那么我们就需要了解一下闭包了:
闭包
关于闭包,即函数定义和函数表达式位于另一个函数的函数体内(嵌套函数)。而且,这些内部函数可以访问他们所在的外部函数中声明的所有局部变量、参数。当其中一个这样的内部函数在包含他们外部函数之外被调用时,就会形成闭包。
也就是说:内部函数会在外部w函数执行后返回。而当这个函内部函数执行时,它仍然必须访问其外部函数的局部、参数以及其他内部的函数。这些局部变量、参数和函数声明(最初时)的值是外部函数返回的值,但也会受到内部函数的影响。
代码如下:
在函数里又定义了一层子函数,子函数被返回了,就是在外层函数执行的时候,返回了子函数的内存地址,在外面执行子函数的时候,又引用了外层函数这个变量,这种现象称之为:闭包。
等等,刚刚视频网站还没有完结......
你仔细看了老王的代码,觉得老王真的不是一般人呀,这种姿势很牛逼呀,你独创的吗?
此时你的媳妇嗤嗤的笑出声来,你也不知道她笑个球
老王说:呵呵,这不是我独创的,这是开发中一个常见的语法,叫语法糖,官方名称“装饰器”,其实上面的语法,还可以更简单:
效果是一样的。
你开心的玩着老王交给你的新姿势,玩着玩着就给你的北京专区模块加了个参数,然后,结果就出错了...
你说:老王,怎么传递一个参数就不行了呢?
老王回答:那是肯定的呀,你调用Beijing的时候,其实是相当于调用的login,你的Beijing第一次调用时Beijing = login(Beijing),返回的是inner的内存地址,第二次用户自己调用Beijing("3P"),实际上相当于调用的是inner,但你的inner定义的时候并没有设置参数,但你给它传递了一个参数,所以这就报错了,你说对不对?
你说:但是我的版块需要传参数啊,你不让我传不行啊!
老王回答:没说不让你传,稍作改动即可!
运行结果如下:
那么,问题又来了,我有的版块没有其他的频道,那么我就不用传递参数,但是如果不传递参数就会报错,怎么办?这时候我们绞尽脑汁,传一个参数可以,不传参数也可以,眼睛一亮,非固定参数!
运行结果如下:
老王:你再试试就可以了
你:果然好使,大神就是大神啊......
老王说:这种姿势多练练,我就先回去了
你的媳妇为了不让打扰你,提出去她的好姐妹家过夜,你觉得你的媳妇真体贴,最终你搞定了所有需求,完全遵守封闭-开放原则,此时你累的已经不行了,洗洗就抓紧睡了,半夜,上厕所,隐隐听到隔壁老王家有微弱的女人的声音传来,你会心一笑,老王这家伙,不声不响找了女朋友也不带给我看看,改天一定要见下真人。
第二2天早上,产品经理又提了新的需求,要允许用户选择用qq\weibo\weixin认证,此时的你,已深谙装饰器各种装逼技巧,轻松的就实现了新的需求。
代码运行结果如下:
希望对你有帮助
领取专属 10元无门槛券
私享最新 技术干货