2019独角兽企业重金招聘Python工程师标准>>>

李哲 — MAY 20, 2015 原文地址:Metaprogramming Dynamic Methods: Using Public_send

作者:Friends of The Web的开发者Vaidehi,OneAPM官方技术博客编译整理。

在上周,我写了一些让我感到非常骄傲的代码!当时,我正努力解决一个有趣的问题,这个问题也是我最近开发的一款应用中所遇到的。于是我把脑海中想到的第一种解决办法很快付诸了实践。然后,当我回过头来查看文本编辑器,并认真审阅完自己所写的代码时,终于意识到:这些代码真的很赞!

一周以来,我不断回顾那些代码段,沉思到底是什么原因令她如此美丽。而我又做了什么不一样的事情,竟让自己的内心充满了骄傲。我认为是元编程,这是我能够第一时间想到的答案。元编程(Metaprogramming)是指某类计算机程序的编写,这类计算机程序编写或者操纵其他程序(或者自身)作为它们的数据,或者在编译时完成部分本应在运行时完成的工作。当然,其中涉及到很多不同的技巧和方法,我也不是专家。但是我的确学到了一个元方法 public_send ,接下来我分享一下自己的使用心得。

GOTTA DISPATCH? DO IT DYNAMICALLY.

其实,不仅仅只是Ruby,编程世界中的一切都只是一种抽象。我们只不过对那些晦涩难懂的机器语言包裹了一层“糖衣”,让程序员的生活变得更为简便,让代码更具美感罢了。但归根结底,这只是对其他事物的一种抽象而已。当我们通过元编程进行重构时,理解的是同样的抽象概念,而且我们必须牢记这一点。同时,当我们试图发现代码中的共性问题时,这也意味着,我们可以封装并抽离那段代码的部分功能。

我最喜欢的抽象实例是method dispatching,通过这种方法我们将信息传递给一个对象,当然,这也是我们经常做的事情。因为Ruby中的所有事物都是一个对象,只要你想调用某个对象完成某件任务,你就不得不向它发送信息。我们应该庆幸,因为Ruby是如此强大,我们用来发送信息的方法就刚好就叫做:send

其实,send方法在程序中被调用的次数远超过我们的想象。比如,当我们打开操控台,做一些简单的数学运算:

2.2.0 > 3 + 4
=> 7

而我们真正做事情的是:给整数3这个对象发送一个信息,告诉它与另一个对象(整数4)进行加法动作。

2.2.0 > 3.send(:+, 4)
=> 7

send 方法用字符串或者一个符号为参数,并以此做为方法名。换言之,方法名总是第一个参数,而第二个参数将以自变量的形式传给该方法。

此时,如果你想执行3加4的加法操作,就会变得很容易。但是,谁会一直做如此简单的加法计算呢?显然没有。你很可能还会执行3加5,加6,一直加到无穷等等。

而使用dynamic dispatching 就可以拯救你!

Dynamic dispatching,就如同上面这幅略显奇怪但是相当可爱的动态图,因为涉及向对象发送不同的信息(read: methods) ,同时因情况的差异,还会产生方法不断改变的警告。Dynamic dispatching 允许我们在程序中面向对象发送不同的方法,而且无需告知其他对象所发送信息的内容。如果你需要再一个特定的环境下调用一个方法,但是不清楚该方法是如何执行的,那么此时就是Dynamic dispatching出场的最佳时机。

我们可以看一下这个例子。

YOU CAN SEND WHUTEVA YOU LIKE

你可以使用 send 方法,来向一个对象“send”不同的方法,但这只占据了一半的乐趣,另一半在于搞清楚什么时候去实际的应用它。

此刻,让我来给大家展示一下,最近如何在应用中使用 Dynamic dispatching 来触发特定的方法。在本文中,我使用了一个电子书店的例子,希望大家能够接受。

在我的书店中,拥有一个开放购买的图书列表,列表以页码为单位来进行展示,且每本书在界面中只有有限的展示空间。作为网站的管理员,我必须决定针对不同的书应该如何进行更好的展示。一些书有非常漂亮的封面,我会想用封面的缩略图作为他们主要的“viewable attribute”,因为我使用了 paperclip 插件,这点很容易实现。

然而,有些书压根就没有封面。比如,浩如烟海的莎士比亚戏剧集,如果以“作者”做为“viewable attribute”,能更好地能吸引读者。而《权力的游戏》系列图书若以书名作为“可见属性”,显然更具吸引力。

所以,我该如何处理这个问题呢?首先,让我们看一下有没有任何共性的存在。

1. Look For Patterns 诚然,我们都希望界面中的每个 Book 对象都能用它最主要的可见属性来展示。我们遇到的问题是管理员会为每个 Book 对象选择不同的属性,再设置其为“viewable”,因此我们无法预测这个属性是titleauthor,还是是一张图像。但是,我们的确知道每个 book 对象都需要某个“viewable attribute”。

这点很酷!所以这里就发现了一个共性:我们需要展示一个属性,但是我们并不知道它会是什么。或者说,其实我们知道?

2. Consider The Data 当下我们为该应用建立一个管理员界面时,我们清楚的知道每本书都有一个title,一个author。书的封面可以是有选择性的(此处我们称其为media ),但另外两个属性则不一定。这意味着我们要对 Book 对象进行一次验证:

    class Book < ActiveRecord::Basevalidates_presence_of :title, :authorend

经过验证值之后,我进一步思考Book 对象肯定也会有的其他属性,首先进入我脑海的就是viewable_by属性。我们可以设想一下,管理员必须将某个属性设为“viewable”,当他更新此对象时,“viewable”就可能改变。因此,对每个 Book 对象来说都是独一无二的,这也意味着我们可以放心地将其保存在数据库中。

所以,我们可以通过代码迁移在数据库中增加viewable_by 属性,并且设置成不可为null,同时将默认值设为 Book’s title

    class AddViewableByToBooks < ActiveRecord::Migrationdef changeadd_column :books, :viewable_by, :string, null: false,            default: "title"endend

这个迁移看起来非常简单,但是,but it is its very simplicity that lends itself so elegantly to some serious metaprogramming that we’ll do next.

3. Encapsulate And Abstract 最后这部分的确最难理解,但也最炫酷。现在,我们的数据库里多了一列,且字符串类型的值是titleauthormedia中的一个,管理员可以随意改变或更新这些值。显而易见,这些值的更改不可避免,但是有一点不变:we’re still going to want to render the value of whatever attribute is marked as “visible” – that is to say, whatever string value is saved as viewable_by.

此时,我们可以回头想想之前定义的那个共性。我们知道,属性会不断改变,但是我们对它的操作仍然保持了一致性。不论Book 对象的viewable_by属性是什么,我们都会展示这个属性。而且我们会跟该对象发送信息:“嘿,书先生,你可见的那个属性,不论它是什么,都是你用来展示自己的值!”

And this is where we can use send to encapsulate and abstract this away into a single method call。首先,我们要增加一个方法来检查可见属性是不是一个图象,如果是的话,我们会将其交给paperclip插件来进行展示:

    def show_cover?self.viewable_by == 'media'end

如果viewable_by属性的值为media,该方法会返回true,否则返回false。我们可以将这个布尔返回值用在一个条件语句中:

    def book_htmlif show_cover?# Code here will generate and return# an html image tag to render in view.elsesend(self.viewable_by)endend

上面的代码怎么了?如此炫酷!book_html方法要么展示一张缩略图(我们会写别的代码来实现),要么返回一个title 对象或者author对象。当然,真正炫酷的地方在于,我们可以在表中添加其他属性,像year或者genre,并在此基础上显示html网页,只需要这些属性保存在viewable列中。

这到底是怎么运行的?其实,每次我们在数据库里创建新列时,我们都获得了两个重要的方法:读和写。这意味着我们拥有了title=title两个方法。

如果我们回顾一下 send 方法的工作原理,就会知道send 会将一个字符串或一个符号作为参数,该参数也是被调用的方法名。当我们调用 send 方法并将self.viewable_by 的值传给它时,我们实际上是在调用一个Book实例的send(“title”) 方法。这会调用该Book实例的title 属性,并将那本书的书名以字符串的形式返回。

这段代码炫酷的地方在于它非常灵活,同时还能将一种共性抽象为动态的方法调用,只在合适的时间对恰当的对象发起请求。但是,这段代码中还存在一个大问题,接下来让我们把它解决掉。

TO SEND OR TO PUBLIC SEND? THAT IS THE QUESTION

很多控诉 send方法的证据都源于此: send会将private methods发送给对象。这在应用内部是相当危险的,而且也让应用在应对外部恶意攻击时显得相当脆弱。

一种快速的修正方法就是使用 pubic_send ,它的功能与你所想的完全一致:只将公共可读取的方法发送给它的接收对象,所以我们最终的代码如下:

    class Book < ActiveRecord::Basevalidates_presence_of :title, :authordef show_cover?self.viewable_by == 'media'enddef book_htmlif show_cover?# Code here will generate and return# an html image tag to render in view.elsepublic_send(self.viewable_by)endendend

赞!作为我们元编程的首次尝试,这段代码并没有显得很寒碜。

虽然做起来很难,但在重构和元编程方面,你也不必太苛求自己。老实说,随着时间的累积,你可以多加练习,逐渐的增长见识,那么编程能力自然也能够得到提高。最终,你会开始察觉那些出现了一次又一次的共性,并开始学会选择正确的工具来解决这些问题。

尽管需要额外的努力,我认为尝试不同的元编程技巧对未来大有裨益,多读多写代码亦是如此。重写之前的代码来实现一些元编程技巧,你可以修改应用中过于死板的代码,将之变得更为灵活、动态。

如果这些话听起来有些吓人,那是因为事实本就如此!但是这并不是不可能的,正如我最近取得的进展,我也希望正在阅读此文的你也能有所斩获。幸运的是,Ruby提供了很多工具,帮我们将死板的代码实现了元程序化。而问题就在于了解那些工具,并在合适的时机能够使用它。我相信,如果你体验了一次元编程,你肯定会欣喜万分,也许还会像小猫一样发出欢乐的尖叫,我想那将是世上最可爱的事情了。

总结一下

在编写程序时,我们可以使用dynamic dispatching将一个方法传递给对象,而不必指明方法的内容。方法sendpubic_send都能实现这一点,它们以字符串或符号为参数,并以此参数作为接受对象调用的方法名。 点击这里学习元编程的基本要素,并查看sendpubic_send方法的相关文档。 如果你还好奇其他的动态方法?可以阅读一下这篇博文,其中深度介绍了一部分方法。


本文作者系OneAPM工程师 ,想好的技术文章,请访问OneAPM官方技术博客。

转载于:https://my.oschina.net/oneapmofficial/blog/418004

翻译 - 元编程动态方法之public_send相关推荐

  1. 【Groovy】MOP 元对象协议与元编程 ( 方法注入 | 使用 Category 分类注入方法 )

    文章目录 一.方法注入 二.使用 Category 分类注入方法 三.完整代码示例 一.方法注入 在之前的博客中 , 主要是使用 Groovy 元编程 拦截方法 , 改变方法的实现 ; 使用元编程还可 ...

  2. python元编程运用_Python 中的元编程

    就像元数据是有关数据的数据一样,元编程就是编写用于操纵程序的某些程序.人们普遍认为,元程序就是生成其他程序的某些程序,但范式更加广泛.所有旨在自我读取.分析.转换或修改的程序都是元编程的范例.例如: ...

  3. 浅谈 C++ 元编程

    随着 C++ 11/14/17 标准的不断更新,C++ 语言得到了极大的完善和补充.元编程作为一种新兴的编程方式,受到了越来越多的广泛关注.结合已有文献和个人实践,对有关 C++ 元编程进行了系统的分 ...

  4. 【Groovy】Groovy 语言特点简介 ( 支持 Java 语法 | 支持 Java 虚拟机 | Groovy 语言是动态语言 | Groovy 扩展 JDK | 编译时元编程 )

    文章目录 一.Groovy 支持 Java 语法 二.Groovy 支持 Java 虚拟机 三.Groovy 语言是 动态语言 四.Groovy 扩展 JDK 五.Groovy 编译时元编程 一.Gr ...

  5. python元编程之使用动态属性实现定制类--特殊方法__setattr__,__getattribute__篇

    问题:实现一个类,要求行为如同namedtuple:只存在给定名称的属性,不允许动态添加实例属性. 主要知识点在于: __setattr__,__getattr__,getattribute__,__ ...

  6. 【Groovy】编译时元编程 ( 编译时方法注入 | 使用 buildFromSpec、buildFromString、buildFromCode 进行方法注入 )

    文章目录 一.在 MyASTTransformation#visit 方法中进行方法注入 1.使用 new AstBuilder().buildFromSpec 进行方法注入 2.使用 new Ast ...

  7. 【Groovy】编译时元编程 ( 编译时方法拦截 | 在 MyASTTransformation#visit 方法中进行方法拦截 )

    文章目录 一.在 MyASTTransformation#visit 方法中进行方法拦截 二.完整代码示例及进行编译时处理的编译过程 1.Groovy 脚本 Groovy.groovy 2.ASTTr ...

  8. 【Groovy】编译时元编程 ( 方法拦截时用到的 AST 语法树节点 MethodNode 节点 | MethodNode 节点分析 | MethodNode 节点中的BlockStatement)

    文章目录 一.方法拦截时用到的 AST 语法树节点 MethodNode 节点 二.MethodNode 节点分析 三.MethodNode 节点中的 BlockStatement 集合 一.方法拦截 ...

  9. 【Groovy】编译时元编程 ( ASTTransformation#visit 方法简介 | org.codehaus.groovy.ast.ModuleNode 脚本节点 )

    文章目录 一.ASTTransformation#visit 方法简介 二.org.codehaus.groovy.ast.ModuleNode 脚本节点 一.ASTTransformation#vi ...

最新文章

  1. 强大的Charles的使用,强大的flutter1.9
  2. “Can’t be opened because Apple cannot check it for malicious software“ 解决方案
  3. Android SlidingMenu 开源项目 侧拉菜单的使用(详细配置)
  4. mysql预编译语句拼接查询_SQL语句预编译(查询)
  5. cxxtest单元测试框架源码分析(二):所有对外功能实现分析
  6. 学习Spring Boot:(十三)配置 Shiro 权限认证
  7. golang 目录分隔符号_Golang 从0到1之任务提醒(一)
  8. web大前端开发中一些常见的安全性问题
  9. 20191226每日一句
  10. 【渝粤教育】电大中专建设工程法规_1作业 题库
  11. 谷歌io开发者大会2018:强大的AI帝国
  12. 计算机图形学 构成立方体数据结构的简单表结构,计算机图形学-沈工大作业管理ppt课件...
  13. 真人语音朗读软件_讯飞语音云助力移动“和阅读”,打造个性化听书应用
  14. 公众号(服务号)申请与认证
  15. L3-015 球队“食物链” (30 分)
  16. 服务器上搭建java环境,安装tomcat以及MySQL数据库-小白教程
  17. 使用破解补丁激活过期的pycharm
  18. 淘系电商再无对手,腾讯为何“资敌”?
  19. 陌生QQ号聊天,QQ咨询对话框,QQ临时对话框链接代码
  20. SSE图像算法优化系列二十五:二值图像的Euclidean distance map(EDM)特征图计算及其优化。...

热门文章

  1. pcre的compile,exec和free的代码
  2. Hasura GraphQL 内部表结构
  3. Linux内核【链表】整理笔记(1)
  4. 长按UIWebView上的图片保存到相册
  5. 《jQuery Mobile入门经典》—— 2.2 展现CSS样式
  6. SharePoint 2013 Step by Step——使用自定义的List Template
  7. 删除windows server backup备份
  8. table或者列表中超出的字用省略号代替的方法(支持IE6)
  9. redhat as4 上安装 MySQL5
  10. 解决“鼠标关机后仍然发光”的方法