Kernel#evel()方法

和Object#instance_evel()、Module#class_evel()方法类似,evel()方法也是一个内核方法,Object#instance_evel()方法可以使调用对象为self,当前类为#self(当前对象的eigenclass),并且传递一个代码块访问self;Module#class_evel()方法则可以使调用者成为当前类,并在当前类中执行传入的块(self此时是一个class的实例对象),从而修改当前类的方法和属性。Kernel#evel()方法则是调用一串代码字符串,并且执行这个字符串中的代码。

*evel()方法都可以传入一个Binding类,Binding类是一个只包含作用域的对象,通过传入Binding类,可以使*evel()方法在Binding类所携带的作用域中执行代码。

Binding类可以通过Kernel#binding()方法创建:

class MyClass
    def my_method
        @a = 1
        binding
    end
end

当执行 b = MyClass.new.my_method时, b就代表了一个作用域,通过 eval “puts @a” ,b 传入该绑定,获得@a。

使用evel方法可以很方便地实现一些功能,如计算器应用的编写等,但是在能力越大的同时,也有更大的危险。Kernel#evel方法执行时不会检查字符串代码的语法,并且会带来一些安全问题:代码注入。当evel方法面向很多其他用户的时候,就可以通过传入一些命令来获得你的私有信息,所以用evel方法时必须非常谨慎,Ruby中,也会有一些安全措施:

Ruby会把不安全的对象标记为被污染的(尤其是从外部传入的对象),通过tainted?()方法可以判断该字符串是否被污染,为了避免检查每个对象的污染情况,Ruby内置了安全级别来默认处理这些危险操作。通过$SAFE全局变量可以改变当前的安全级别,默认为0——不受任何约束;一共有5个安全级别,任何大于0的安全级别都不能执行污染的字符串。

为了安全地使用evel,可以为evel()方法创造一个沙盒,并且在沙盒中运行该字符串:

proc{
    $SAFE = @safe_level
    evel “cmd”,b
}.call

evel()方法的替代

为了避免evel()带来的安全问题,可以使用其他的方法来替代evel():通过动态派发:send()方法来调用方法;通过class_evel()来进入类,并为类添加方法或者实例变量,并且用define_method()方法来动态添加方法;使用Object#instance_variable_set()和Object#instance_variable_get()方法来设置或者访问实例变量。

钩子方法

通过改写Class#inherited()方法、Module#included()方法、Module#method_added()方法等等,可以在相应的事件发生时执行所需要的代码。如在包含一个模块的时候打印提示:

module M
    def self.included(othermod)
        puts “M was mixed into #(othermod)”
    end
end
class C
    include M
end

在C类中包含M模块时,会打印:M was mixed into C 提示字符串。

钩子方法默认实现时只是捕获一个事件,并不会执行其他的动作, 可以通过改写Module#include()方法来完成上述功能:

class C
    def self.include(*modules)
         puts “#(modules) is included in C”
         super
    end
    include M
end 

上述方法直接修改了include方法,由于include()方法除了捕获到事件之外,还有其他的事情要做,所以需要通过super来进行原始的工作,其中super的作用是在父类(当前类是C,父类是Module)中调用同名的函数,并且将本函数的所有参数传入到同名函数中。还有一个方法super()带括号,则表示调用父类中的同名函数,但是不传入任何参数。*表示传入多个参数打包为一个数组,并且在方法调用时解开数组使每个元素成为一个独立参数,通过*modules可以一次包含多个模块。

通过环绕别名来实现钩子方法:

class C
    Class.class_eval {
        alias :real_include :include 
        def include (mod)
            real_include mod
            puts "#{mod} was included!"
        end
    }
    include M
end

类扩展混入

当一个类包含模块时,只会获得一组实例方法,而不会获得任何类方法,通过在eigenclass中包含一个模块来实现类扩展(或者使用extend()方法),如果一个模块期望被包含时一直可以作为类扩展,则可以通过在模块中添加钩子方法来实现:

module M
    def self.included(base)
        base.extend(ExtendMethods)
    end
   
    module ExtendMethods
        def my_method()
            #…
        end
    end
end

此时,如果在类中include M,则会调用钩子方法M.included(),将M中的子模块ExtendMethods(纯净室)作为类扩展,将Methods中的方法添加到包含类的eigenclass中。

上述将类扩展和钩子方法结合的技术叫做类扩展混入。而且,如果在M中又不需要被扩展的方法,则可以放到ExtendMethods外部定义一些额外的方法,这些方法不会被扩展为包含着的类方法。如果M中所有的方法都需要被扩展为类方法,则把所有的方法都定义在M本身即可。 

Ruby学习之元编程的更多相关文章

  1. Ruby学习: 类的定义和实例变量

    ruby是完全面向对象的,所有的数据都是对象,没有独立在类外的方法,所有的方法都在类中定义的. 一.类的定义语法 类的定义以 class 关键字开头,后面跟类名,以 end标识符结尾. 类中的方法以 ...

  2. ES6中的元编程-Proxy & Reflect

    前言 ES6已经出来好久了,但是工作中比较常用的只有let const声明,通过箭头函数改this指向,使用promise + async 解决异步编程,还有些数据类型方法...所以单独写一篇文章学习 ...

  3. C++模板元编程----选择排序

    目录 目录 前言 代码详解 数据的结构 数据的操作 分割向量 合并向量 寻找最大值 排序 总结 前言 模板在C++一直是比较神秘的存在.STL和Boost中都有大量运用模板,但是对于普通的程序员来说, ...

  4. 3-20 标准库:find库; 学习编程语言3节课(大多是旧识,全*栈)3-21 面向对象. Percent Strings; 元编程和Rails的相互理解

    Find The Find module supports the top-down traversal of a set of file paths.(一系列文件的路径的遍历) find(*path ...

  5. 3-11 《Ruby元编程》第4章block块 3-12

    第4章代码块blocks 基础知识 作用域:用代码块携带variables through scopes 通过传递block给instance_eval方法来控制作用域. 把block转换为Proc, ...

  6. 3-8《Ruby元编程》第二章对象模型

    <Ruby元编程> 第二章 对象模型 类定义揭秘inside class definitions: class关键字更像一个作用域操作符,核心作用是可以在里面随时定义方法. [].meth ...

  7. Ruby元编程:动态添加类属性及其实际应用

    上个星期测试道的Monkey老师和我聊到测试用例参数过多的问题,其实这样的问题在我这里也同样经历过.比如我的测试用例必须面对不同的测试环境,每个环境有无数的参数,开发的最初阶段,因为参数少,所以就放在 ...

  8. Ruby元编程:单元测试框架如何找到测试用例

    前几天看了Google Testing Blog上的一篇文章讲到C++因为没有反射机制,所以如何注册测试用例就成了一件需要各显神通的事情.从我的经验来看,无论是Google的GTest还是微软的LTM ...

  9. 201707《Ruby元编程》

    元编程不过是编程--经典必读 作用域(绑定) 打破作用域门的方式 对象模型图 七条规则 法术手册 作用域(绑定) 改变作用域的关键字, 分别是module,class和def.我们称为作用域的门(sc ...

随机推荐

  1. php结合redis实现高并发下的抢购、秒杀功能 (转载)

    抢购.秒杀是如今很常见的一个应用场景,主要需要解决的问题有两个: 1 高并发对数据库产生的压力 2 竞争状态下如何解决库存的正确减少("超卖"问题) 对于第一个问题,已经很容易想到 ...

  2. 通过YUM升级centOS内核,以便安装docker

    安装Docker要满足一定的条件,对于cents系统,要求必须是64位,并且内核版本是3.10以上. 如果你的centos操作系统内核低于3.10,需要升级到这个版本以上,才能安装docker. 第一 ...

  3. jsp内置对象 的使用范围和类型【说明】

    jsp内置对象 jsp内置对象有以下9种,我们会在后面的章节中分别介绍他们.这9种对象例如以下: 名称 类型 使用范围 request javax.servlet.http.HttpServletRe ...

  4. 求前n个素数(C++)

    输入一个输n,输出前n个素数. #include<iostream> #include <math.h> using namespace std; class Sushu { ...

  5. Tkinter界面编程(一)----函数分析

    Tkinter模块是python比较常用的GUI界面设计模块,首先对相关的函数进行分析. 一 .创建根窗口相关的函数说明 import tkinter as tk top = tk.Tk() # 创建 ...

  6. nginx+redis实现session的共享

    上一篇我们介绍了nginx实现的负载均衡和动静分离,可看这边. 我们在文章的末尾说到,负载均衡需要面临的一个问题是内存数据的同步.例如:我有A,B两台服务器做了负载均衡,当我在A服务器上执行了登录并且 ...

  7. android 事件传递机制(1)

    在项目中,经常遇到事件冲突,ScrollView,ViewPager滑动卡顿等情况,比如:onClick和onLongClick事件冲突,dispatchTouchEvent,onInterceptT ...

  8. 登录验证码demo-java

    在一些类似于管理系统的项目中,我们在登录时经常会用到图片验证码.这里把我自己写的一个小系统(后台是java语言)的验证码部分摘出来. 总体思路是后端有一个生成验证码图片的接口,把验证码图片写入浏览器, ...

  9. hibernate框架(4)---主键生成策略

    主键生成策略 常见的生成策略分为六种 1.increment 由Hibernate从数据库中取出主键的最大值(每个session只取1次),以该值为基础,每次增量为1,在内存中生成主键,不依赖于底层的 ...

  10. git的merge功能

    merge功能是将一些分支的内容合并到某一个特定的分支,这里我为了测试下,在阿里云code上面新建了一个项目 现在我需要将dev分支merge到主分支master 开发者和管理员都有权发起merge请 ...