我眼中的元编程-对象模型篇

作为一个Ruby开发者,让人又爱又恨的便是元编程了。

【前言】元编程是什么

简单地说,元编程就是对语言本身的进行操作的一种编程手段,最常见的就是代码生成代码。对于Ruby这门语言而言,不会元编程,等于不会这门语言,因为这是它的核心能力与魅力。本文是基于阅读《Ruby元编程》后记录的一些自己的理解和看法。

元编程示例【示例1】
module Kernel
  def attr_access(*args)
    args.each do |arg|
      define_method(arg) do
         instance_variable_get("@#{arg}")
      end
      define_method("#{arg}=") do |val|
        instance_variable_set("@#{arg}", val)
      end
    end
  end
end

class Student
  attr_access :name, :age
end

stu = Student.new
stu.age = 20
stu.name = 'Rapheal'

p stu.inspect

【示例1】一个典型元编程的例子,它实现了Ruby中自带的attr_accessor相同的功能,作用是动态的为传入的参数(上面代码中是:name:age)添加settergetter方法(stu.age=xxx为其setter方法, stu.age为其getter方法)。这样的方法避免了类似Java中的长篇settergetter定义。

【主题】对象模型

Ruby作为一种完全面向对象的编程语言,即使是一个数字、类、甚至一个方法都是一个对象。所谓对象,就是能对它进行一系列操作的一个集合。

打开类

对象模型篇第一讲就是打开类。在【示例1】代码中其实就已经包含了打开类的一种具体实现方法。打开类,即打开一个已经存在的类或对象,为其添加新方法修改已存在的方法删除不需要的方法的一种技术。在【示例1】中,Kernel是Ruby库中已经存在的一个模块,使用module Kernel将其重新打开,并添加了一个新方法attr_access。于是Kernel模块便在原来的基础上新增了一个方法attr_access

修改一个已经存在的方法【示例2】
str = "abc"
p str.to_s # 这里会输出"abc"
class String
  def to_s
    "Nothing"
  end
end
str = "abc"
p str.to_s # 这里输出的就是"Nothing"了

String也是Ruby库自带的类,to_sString类已存在的方法,当重新打开它并重写了to_s方法之后,原来方法的作用便不复存在了,取而代之的是新方法的作用。(这种修改已经存在的方法又被称为猴子补丁

打开类的利与弊

通过【示例1】【示例2】的代码可以知道,打开类技术可以很好的对已经存在的类或方法进行修改,使之更符合个人的使用需求。然而,若不加以思考随意使用,带来的问题也是很严重的。比如String类的to_s方法,作用就是要返回本身这个字符串,结果被别人修改了这个定义,导致了所有引用这个方法的代码全部失去了它本来的功能与意义。因此在使用打开类定义一个方法时,需要谨慎,尽量取一个当前不存在的方法名来新定义一个方法

对象中有什么

首先,实例变量,如【示例1】中的:name:age,当调用stu.name = 'rapheal'之后,stu对象便产生了一个实例变量@name实例变量必须是以【一个@符号】开头的变量名。这时可以通过调用stu.instance_variables来查看已经存在的实例变量,可以看到输出中有:@name这一条。

其次,方法。通过stu.methods可以查看stu对象能调用的所有方法。Ruby对象共享方法,但不共享实例变量,共享的方法被称为【实例方法】。【实例方法】定义在对象的类中,这样可以使得同一类对象可以调用相同的方法。

类也是对象。类对象所属的类是Class类。类的方法即为Class类中定义的【实例方法】。比如,所有类都有一个方法new,而new方法的定义就在Class类中。我们甚至可以简单的认为:ClassA = Class.newclass ClassA; end是等价的。它们都是在定义一个新的类ClassA

方法查找

提到方法查找,那么首先要知道的就是祖先链祖先链其实就是记录的一个类的继承关系的一个列表,可以通过调用ancestors方法来查看。比如String.ancestors返回的是[String, Comparable, Object, Kernel, BasicObject],于是我们可以判断,String类继承自Object,(ComparableKernel是两个module,它被包含在了其中的某个类中,也会出现在祖先链中来,此处我们不讨论祖先链中的module),Object又继承自BasicObject

理解了方法链,再回头来看方法查找。Ruby中的方法查找有个原则叫作向右,再向上。比如,有一个String类的对象str,调用方法str.test_call_method,这时Ruby解释器会:

  • 1、【向右】来到str所属的String类查看String类是否定义了test_call_method这个方法,若定义了则直接调用
  • 2、【向上】否则查看Comparable这个module中是否定义这个方法(因为祖先链中有这个module,并且排在了第二个,即String类和Object类中间)
  • 3、【向上】若还未定义,则来到父类Object类查找
  • 4、重复上述2、3步骤直到BasicObject

上述步骤中,步骤1称为向右步骤2、3称为向上。整个流程中,可以看出,方法查找是优先向右(所属类)查找,再向上(优先是自身包含的模块然后是父类)查找。因此称为向右,再向上原则。

对于类所包含的模块会在方法查找时定义为一个匿名类并插入到祖先链中该类的直接上方

关于self

在某个特定时刻,一定会有一个指定的对象在执行,这个对象就是self对象。最开始接触这个的时候,会有一个误区认为self是当前调用方法的执行者,然而事实上self是当前方法执行的接收者。简单说即是,当前方法调用的结果会传递返回给这个self对象。

谈到self,那么就应该顺便说一下private。Ruby中的private是和self相关的,在Ruby类的定义的private方法是不能被显式调用的。

private示例【示例3】
class A
   def print_self
      self.t_pri
   end
   def print_self_2
      t_pri
   end
   private
      def t_pri
        p "hello world"
      end
end

obj1 = A.new
obj1.print_self_2 # 输出 "hello world"
obj1.print_self     # 报错, NoMethodError: private method 't_pri' called
obj1.t_pri            # 报错,同上

【示例3】,可以看出私有方法t_pri只能由self隐式调用,即私有方法只能在定义的内部以直接调用方式调用,而不能在任何地方以 xxx.yyyy的方式调用。同时,若没有显式指定方法接收者,那么调用方法的接收都将隐式指定为self对象。

第一章对象模型基本上就这些内容,了解对象基本模型对于以后编写Gem包等扩展有相当重要的作用,尤其是self对象。以后的章节讲到扁平作用域的时候会对self对象有更加深刻的描述,如果这些技能不熟练,要编写Gem扩展真的是举步维艰。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,651评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,622评论 18 399
  • 20- 枚举,枚举原始值,枚举相关值,switch提取枚举关联值 Swift枚举: Swift中的枚举比OC中的枚...
    iOS_恒仔阅读 2,278评论 1 6
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,951评论 6 13
  • 当你在明亮舒适的休息室,喝着水,不断敲打键盘,偶尔看看窗外,现在的我除了毕业的事忙碌一些,也没有其他的事了,很快就...
    愿暖阅读 110评论 0 0