zoukankan      html  css  js  c++  java
  • Ruby学习之代码块

    代码块在其他的语言中都或多或少接触过一些,如perl中sort{$a<=>$b}keys,传入代码块实现按数值排序,在swift中用到闭包,更加深入学习到training closure、capturing value等代码风格,对代码块有了深入的了解,并且意识到代码块是引用类型(Reference Type),和Value Type有所区别,意识到代码块和类、方法等的相似之处。

    在学习Ruby的过程中,对代码块的理解更加加深一步,不仅仅是简化代码的功能,还涉及到作用域、可调用对象等知识。

    代码块

    代码块定义在大括号或者do...end关键字中,块被传递到方法中时,可以以yield关键字回调,同时能够通过Kernel#block_given?()方法检测是否传入了代码块:

    class MyClass
        def my_method
            return yield if block_given?
            "There's no block"
        end
    end
    obj = MyClass.new
    puts obj.my_method{"There's a block!"}
    puts obj.my_method

    块也可以有自己的参数,在||中用逗号隔开,调用时用yield(arg)就能调用:

    def my_method
        return yield(2,3)
    end
    puts my_method {|x,y| "The two numbers are #{x} and #{y}”}

    以上语句会输出 The two numbers are 2 and 3

    绑定

    块不仅仅可以通过调用参数的方式和其他语句交流,更加重要的是,块在定义的时候,会从当前作用域获取到绑定:

    def my_method
        return yield(2,3)
    end
    z = 4
    puts my_method {|x,y| "The there numbers are #{x},#{y} and #{z}"}

    这理解起来并不难:在过程语句中,每一条语句都可以访问到当前作用域的其他变量,块也是一样,把可以运行的代码看做两部分组成:代码本身和一组绑定。

    如果还是有些困惑的话,是因为表示代码块的{}并不意味着新的作用域,请注意Ruby中是没有{}表示开始结束的,而是用了类似python的风格。

    作用域

    那么在Ruby中,是什么真正影响了作用域呢?其实只有三个关键字会开启新的作用域,称为“作用域门”:

    class、module、def

    一旦碰到这三个关键字的其中一个,就意味着进入了新的作用域,并且在end关键字出现时离开,这里可以理解为作用域门就是左大括号{,end可以看成右大括号},如果你愿意这么理解的话。class、module和def有微妙的差别:class和module定义中的代码会被立即执行,而def定义中的代码只有被调用的时候才会被执行。

    由于作用域仅仅由这三个关键字来决定,那么可以通过一些方法穿越作用域门,而让代码块获得上方作用域的绑定:用Class.new()、Module.new、Module#define_method()方法来代替class、module和def关键字:

    var = "top level obj"
    MyClass = Class.new do
        define_method :my_method do
            puts var
        end
    end
    obj = MyClass.new
    obj.my_method

    如上述代码,在my_method中任然可以访问到var,而不会受到作用域门的阻隔,这种方法成为扁平作用域,通过利用作用域门和穿越作用域门,可以灵活地共享作用域以及作用域保护。

    打破封装

    在作用域中,还有一种黑科技:Object#instance_eval()方法。这个方法可以传递一个块,作为上下文探针,该方法调用的块的接收者会成为self,此时,该块可以访问接收者的私有方法和实例变量,甚至可以在不碰其他的绑定情况下,修改self对象:

    class MyClass
        def init
            @v = 1
        end
        attr_reader :v
    end

    obj = MyClass.new
    obj.instance_eval {
        @v = 2
    }
    puts obj.v

    此时,obj的v属性已经被修改了!所以为什么称它为黑科技了,因为他可以打破封装结构,同时它还有另外的作用,制作洁净室

    洁净室是那些只为了执行其中的代码块的类:

    class CleanRoom
        def complex_calculation
            #…
        end
       
        def do_something
            #…
        end
    end
    clean_room = CleanRoom.new
    clean_room.instance_eval{
        if complex_calculation > 10
            do_something
        end

    以上就是一个洁净室的例子

    可调用对象

    块的使用过程分为两步:打包和调用。将块打包后方便以后调用的方法有三种:proc、lambda、使用方法。

    在Ruby中,绝大多数东西都是对象,但是块除外,如果想要将块打包存储,则需要调用一些类来获取帮助。

    Proc就是其中的一种类。在新建Proc类时,会将调用的块存储,并在之后可以通过call来调用:

    obj = Proc.new{"This is a block"}
    puts obj.call

    除了Proc.new之外,Ruby还提供两个内核方法用来将块转换成Proc:proc()、lambda(),其中proc()可以看成是Proc.new的一个别名(Ruby1.9之后),他们的区别是lambda更加像一个方法,他的return会从lambda中返回,而proc仅仅表示从当前作用域中返回,以及其他的一些不是很清楚的区别。

    和在swift中类似,代码块可以看做是方法的最后一个隐含参数,也可以显示地命名它,要求使用&符号开头(这里没有任何联系,但是我记忆的时候将他理解为:因为块是引用类型,所以用&符号传入引用):

    def my_method(a,b,&operation)
        #...
    end
    my_method(1,2){
        #...

    此时,用&命名也是一种存储块的方式,通过这种方式可以让块的内容在多个方法之间传递,或者将这个块传递给另外一个Proc。

    &符号的真实含义:将一个Proc对象作为块来使用,简单地去掉&,就可以再次获得Proc对象。直到了这点,也可以在其他地方,利用&Proc来将Proc转换回一个块。

     

  • 相关阅读:
    应用Dubbo框架打造仿猫眼项目 理解微服务核心思想
    慕课网--docker走进第一个javaweb应用
    金融行业微服务架构解析-转载炼数成金架构模块金融行业微服务架构解析
    StringEscapeUtils防止xss攻击详解
    尚硅谷 dubbo学习视频
    【面试篇】寒冬求职之你必须要懂的Web安全
    echo "" > 和 echo "" >> 的区别
    python函数、模块、包
    python学习笔记(7)--循环语句
    python学习笔记(6)--条件分支语句
  • 原文地址:https://www.cnblogs.com/lyon2014/p/4416007.html
Copyright © 2011-2022 走看看