建一个网站做cpa联盟,石英石台面做网单有什么网站,电脑制作网站用哪个软件,wordpress调整配置文件怎么写本文总结了《Ruby元编程》的基础部分#xff0c;这一部分会以对象模型出发#xff0c;探讨各个知识模块儿间的关系#xff0c;提出问题#xff0c;并附带代码演示#xff0c;会在之后Rails的学习中更清楚底层的编程技巧。 适用人群#xff1a;Ruby基础语法学习后 元编程是… 本文总结了《Ruby元编程》的基础部分这一部分会以对象模型出发探讨各个知识模块儿间的关系提出问题并附带代码演示会在之后Rails的学习中更清楚底层的编程技巧。 适用人群Ruby基础语法学习后 元编程是什么
元编程是编写能在运行时操作语言构件的代码
在我们使用编辑器编写代码后代码按照我们所写的内容进行编译运行。在启动后我们无权再对代码进行干涉而在一些业务场景中我们想在不修改源代码的前提下对一个类进行增强这在Java中也是一个成熟的技术例如反射动态代理。但是Java所能给予的操作相比Ruby就显得格外的严格。
Ruby是一门可以在运行时操作语言构建的工具。语言构建就是我们代码中的各个成员变量类方法。通俗来说可以使用Ruby在运行时对已有的类进行灵活的修改例如修改一个方法的定义。实例变量的定义甚至我们可以在运行时创建一个没有的类。下面我们使用一些伪代码来进行演示。
我们想对数据库进行操作最初我们的想法就是写一个Entity基类然后由子类继承
class Entity# 提供访问器attr_accessor :table,:iddef initialize table,idtable tableid idDatabase.sql insert into #{able} id values #{id}}enddef set(col,val)Database.sql update #{table} set #{col} #{val} where id #{id}enddef get(col)Database.sql select #{col} from #{table} where id #{id}end
endclass Movie Entitydef initialize idsuper movies,idenddef titleget titleenddef title valueset title,valueenddef articleget articleenddef article valueset article,valueend
end
# --------------------------------
# 插入一条数据简单且方便
movie Movie.new(1)
movie.title 猫捉老鼠
movie.article 相当不错 上面的代码看起来是可以解决问题但如果一张表的字段特别多我都需要定义到Movie类中吗能不能用更少的代码解决问题我们使用Active Record类库操作
class Movie ActiveRecord::Base
end
# --------------------------------
# 插入一条数据简单且方便
movie Movie.new(1)
movie.title 猫捉老鼠
movie.article 相当不错 我们看到这次在Movie继承ActiveRecord::Base后没有指定是哪个数据表没有写SQL没有定义像上面一样的操作方法我们就可以轻松插入数据。这底层到底是干了什么
实际上是ActiveRecord在运行期通过内省机制查看类的名字通过Movies推断出表名为movies,并在在读取数据表时发现有title,article两个字段动态的定义了两个同名的属性和相应的访问器。也是就动态的生成了 Movie#title 和 Movie#title 这样的方法。
这就是Ruby的特点也是我们要学习的元编程的一种表现形式我们后面将试着分析和学习它。
对象模型
现在我们有一个需求将给定的字符串添加一个后缀我们可以定义一个函数
def appendTxt(content) content content .txt
endp appendTxt alibaba # alibaba.txt 但这不填符合我们面向对象的方法应该将这个函数封装到一个具体类中定义它的职责。如果我们因此封装一个ApendTxtString类会不会导致类太多了能不能让原本Ruby中的String具有新的行为,答案是可以的。我们可以直接修改Ruby原先定义的标准类。
class Stringdef appendTxtto_s .txtend
end
# 我们使原先的String具有了新的行为
p alibaba.appendTxt class更像是一个作用域操作符当你第一次使用class时会判断是否有这个类如果没有进行创建如果有则带回到类的上下文中可以修改以往的方法实例等等这带给开发者很大的灵活性但如果使用不当也会导致很大的问题比如原本String拥有appendTxt方法而很多地方都应用这个函数而你一旦从新定义就会导致全局的bug而且不容易排查所以使用前一定要检查是否有重名操作。这在Ruby中也称为猴子补丁Monkeypatch后面我们也有一些其他办法来替代猴子补丁如细化Refinement
类的真相
# 我们定义了一个类
class MyClassdef my_method# 定义实例变量v 10end
end
# 创建实例
obj MyClass.new
# 输出当前对象是哪个类的实例
obj.class # MyClass 如果可以用Ruby解释器查看obj对象内部我们可以发现什么 内部只有一个v实例变量而且仅属于obj。my_method并不属于obj而是在MyClass中定义这也是为什么类实例可以共享类的实例方法的原因
实例变量
obj.my_method
p obj.instance_variables # [:v] 与Java不同Ruby中对象类和他的实例变量没有任何关系当你赋值创建一个实例变量时它就出现了如果你不使用obj.my_method这个实例就没有v
方法
# 查看实例拥有哪些方法因为每个类实例都是继承了Object类所以会继承很多的方法
p obj.methods
# 这里用正则筛选一下
p obj.methods.grep(/my/) 一个对象内部其实只包含了自身的实例变量和对自身类的引用方法并不在对象中而在类中。这就是同一类的实例共享方法但不共享实例变量的原因。
# String实例的方法
p String.instance_methods
# String实例方法类方法
p String.methods
# String忽略继承的方法
p String.instance_methods(false)类的真相
在Ruby中类本身其实也是对象是另一个类的实例。
# String类实际上是Class类的一个实例
p String.class # Class
# 而Class类也是Class的实例
p Class.class # Class 这确实是挺绕的。不过这么看来一个类的方法就是Class的实例方法。
p Class.instance_methods(false) # [:allocate, :superclass, :subclasses, :new] 这里看到Class的实例方法有四个其中new是我们最常用的allocate是new方法的支撑方法而superclass与我们Java中熟悉的继承有关找到他的父类是谁
p String.superclass # String的父类Object
p Object.superclass # Object的父类BasicObject
p BasicObject.superclass # BasicObject的父类nil 空 到头了p Class.superclass # Class的父类是 Module
p Module.superclass # Module的父类是 Object 可以看到Class是继承了Module并自身定义了实例化的操作。所以他们看起来那么像。
每个类最终继承与BasicObject而每个类又是Class类的实例。
常量
任何大写字母开头的引用都代表着常亮而常量一般指不会修改的东西在Ruby中常亮也可以看做是变量而当你修改编译器会发出警告但仍然会进行修改。常量与变量最大的区别在于作用域的不同。
module MyModuleMyConstant Outer constantclass MyClassMyConstant Inner constantend
end
p MyModule::MyConstant
p MyModule::MyClass::MyConstant 这里内部的MyConstant和外部的MyConstant实际上处于两个作用域中是完全不同的东西而我们可以通过::来访问他们
如果处于模块儿较深的位置想用绝对路径来访问外部的常量可以使用 :: 表示路径的根位置。
Y a root-level constant
module MY a constant in M::Y # a root-level constant
end Module.constants会返回当期那范围内所有常量这里需要注意一点Module中定义class其类名也是一个常量。如果想知道当前代码所在路径则可以知道Module.nesting方法。
而我们一般会利用常量域的不同而作为名称空间这样避免类名冲突
module Mclass MyClassdef hiputs helloendend
end
mc M::MyClass.new
mc.hi方法查找
这里有两个概念接收者和祖先链
比如说上面的代码 mc.hi ,其中mc就是方法的接受者在执行这个方法前需要先找到这个方法的定义所以先到接收者中去找该方法如果没有则找他的父类或者是引入的Module中寻找而接收者接受者内引入模块儿父类共同构成了祖先链。
module M1def m1_methodm1.methodend
end
module M2def m2_methodm2.methodend
end
class MyClass# 引入M1模块儿在祖先链中位置为自身类上面include M1 # 引入M2模块儿在祖先链中位置为自身类下面prepend M2
end
class AC MyClass
end
# [AC, M2, MyClass, M1, Object, Kernel, BasicObject]
p AC.ancestors # 查看他的祖先链如果在祖先链中多次引入一个module会怎么样 module M1; end
p M1.ancestors # [M1]
module M2include M1
end
p M2.ancestors # [M2, M1]
module M3prepend M1include M2
end
p M3.ancestors # [M1, M3, M2]Kernel模块儿
在Ruby中我们常常使用print就好像所有对象都有print方法一样。但实际上这些方法来着Kernel模块儿私有实例方法因为Object引入了Kernel所以每个对象都可以调用Kernel方法也叫做内核方法我们当然也可以自己加入自己的方法 这里说明一下为什么我们在顶级作用域下写的函数代码可以直接运行 p self # main
p self.class # Object 顶级作用域下实际上是self指向main实例对象main又是Object的子类所以继承Object的实例方法所以我们在顶级作用域下调用 print 方法是调用Object::Kernel#print 方法。
执行方法 当我们找到了该方法如何去执行呢 比如我们现在找到了该方法
class MyClassdef initializex 1enddef my_methodp self # #MyClass:0x000000010dcdb320 x1 self为objtemp x 1end
end
obj MyClass.new
obj.my_method # obj调用my_method时obj为当前对象self
p self # main , 在顶级作用域下调用 p ,接受者为main对象main为当前对象请问x是属于哪个对象的my_other_method属于哪个对象 一般情况下会将最初方法的接收者作为当前对象也就是作为self所有实例变量和方法都属于self如果没有明确指定接受者的实际上都指向self除非转而调用其他对象的方法则self就会转为这个对象。 Ruby中当前对象的转换与作用域的转化是很绕的需要好好理一下 Ruby中 private 修饰的方法不能明确指定接受者来调用私有方法只能通过隐性的接受者self调用。 class MyClassdef hip Hi enddef Hello# 这里Hello 调用 私有方法hello 使用的隐藏当前对象,也就是MyClass的实例对象helloendprivatedef hello p helloend
end
obj MyClass.new
obj.hi # 正常调用
obj.Hello # 正常调用
obj.hello # 无法调用因为private方法不能指定接收者调用只能隐性调用顶层上下文 如果没有调用任何方法那这时谁是self呢 # main
p self
# Object
p self.class在Ruby程序运行时Ruby解释器创建一个名为main对象作为当前对象这个对象有时被称为顶层上下文。
细化
在前面我们使用了猴子补丁对原有的类进行修改但这一操作是全局性的如果把控不好会导致许多隐性的Bug所以Ruby又引入了细化(refinement),起到同样的作用但是可以限制作用域。
module StringAppend# 喜欢 String 这里标准类库传入一个Blockrefine String do# 在Block内定义一个append_txt方法def append_txtto_s .txtendend
end
module StringStuff# 上面定义好并未生效需要主动启动 usingusing StringAppend# 这里正常执行p alibaba.append_txt
end
# 这里就会报错所以通过细化可以控制修改的访问范围不会使全局都看到这个修改
p taobao.append_txt 细化只在两种场合有效
refine代码块内部using语句位置到模块儿结束或者到文件结束在顶层上下文使用using
细化的陷阱
class MyClassdef my_methodp old methodenddef other_methodmy_methodend
endmodule MyClassRefinementrefine MyClass dodef my_methodp new methodend end
end# 在顶级上下文中使用using
using MyClassRefinementobj MyClass.new
obj.my_method # new method
obj.other_method # old method这里虽然使用了细化但当其他实例方法调用细化方法还是会调用之前定义的代码如果直接调用细化方法则修改为细化内容。
方法
在Ruby这种动态语言中方法的调用是极为灵活的并不会在编译器就爆出各种错误比方说我定义一个User类我想调用hi方法我并没有定义hi方法但这并不妨碍我编写代码我甚至可以说我去找hi方法发现没有这个方法最终我向这个类中添加我想要的方法在运行期间。这给了我们更多的操作空间这将是我们要学习的。 现在我们有一个老的系统需要我们重构老板要求系统自动为超过99美金的开销天添加标记 class DSdef initialize ; end# 连接数据源def get_cpu_info(id) ; enddef get_cpu_price(id) ; enddef get_mouse_info(id) ; enddef get_mouse_price(id) ; enddef get_keyBoard_info(id) ; enddef get_keyBoard_price(id) ; end
end
ds DS.new
# 获取信息
ds.get_cpu_info(1)
# 获取价格
ds.get_cpu_price(1)
ds.get_mouse_info(1)
ds.get_mouse_price(1)我们现在需要将数据源封装起来每个computer为一个对象并为每个组件定义通用的方法
class Computer# data_source 就是上面的DS对象def initialize(computer_id,data_source)id computer_iddata_source data_sourceenddef mouseinfo data_source.get_mouse_info(id)price data_source.get_mouse_price(id)result Mouse: #{info} : (#{price})result * result if price 99return resultenddef cpuinfo data_source.get_cpu_info(id)price data_source.get_cpu_price(id)result Cpu: #{info} : (#{price})result * result if price 99return resultend# ... 类似操作
end 我们可以看到 mouse 和 cpu 就有大量的代码重复如果后面还需要加其他的则会让代码臃肿且冗余。
我们有两种办法进行重构优化动态方法 method_missing
动态方法
动态调用方法
class MyClassdef method(content)end
end
obj MyClass.new
obj.method Hello World
# 动态派发和上面普通调用的结果是一样的, 将 obj.method 替换为 obj.send(:method)
obj.send(:method,Hello World)为什么使用动态派发 因为可以在运行最后才决定具体调用哪个方法。而不是硬编码决定 这里使用:method而不是method,实际上是一样的 obj.send(method,Hello World)
:method 表示的是一个Symbol符号
method则是一个String字符串一般在元编程我们常常使用Symbol因为Symbol是不可变的。字符串是可变的。
动态定义方法
class MyClass# 这里就是定义了一个实例方法 将 def my_method 替换为 define_method :my_method参数部分通过Block传递define_method :my_method do |my_arg|my_arg * 3end
end
obj MyClass.new
obj.my_method 2 在运行时定义方法的技术称为动态方法。 为什么使用动态方法而不是直接定义def 因为这样可以在运行最后决定方法名叫什么
重构Computer类
class Computerdef initialize(computer_id,data_source)id computer_iddata_source data_sourceenddef mousecompanent :mouseenddef cpucompanent :cpuenddef companent(name)# 这里使用了动态调用方法info data_source.send(get_#{name}_info,id)price data_source.send(get_#{name}_price,id)result #{name}: #{info} : (#{price})result * result if price 99return resultend# ... 类似操作
end 我们使用动态派发的方式抽离出一个公共组件其他配件可以直接使用代码量减少的多
我们再用动态定义方法去试着重构一下代码
class Computerdef initialize(computer_id,data_source)id computer_iddata_source data_sourceend# 这里定义一个类方法这里的self指向的是Computer类常量def self.define_companent(name) # 根据传入的Symbol创建相应的方法define_method(name) doinfo data_source.send(get_#{name}_info,id)price data_source.send(get_#{name}_price,id)result #{name}: #{info} : (#{price})result * result if price 99return resultendend## 这里主动调用 并动态创建对应的方法define_companent :mousedefine_companent :cpudefine_companent :keyboard
end现在Computer已经剩不了多少代码了我们使用内省方式缩减代码 class Computerdef initialize(computer_id,data_source)id computer_iddata_source data_source# 主动根据DS中给定的访问方法创建访问方法而不需要我们再去手动控制data_source.methods.grep(/^get_(.*)_info$/ |) {# 被正则表达式匹配到的方法会依次调用这里传递的块儿并将内容封装到 $1 全局变量中Computer.define_companent $1}enddef self.define_companent(name)define_method(name) doinfo data_source.send(get_#{name}_info,id)price data_source.send(get_#{name}_price,id)result #{name}: #{info} : (#{price})result * result if price 99return resultendend
end 我们在初始化方法中加入几场代码就可以让代码更加简洁。
method_missing方法
class Userdef method_missing(method,*args)puts 我是所有丢失消息的重点end
endobj User.new
obj.hi 在Ruby中我们可以随意调用一个方法而这个方法可能根本不存在当运行时在当前对象的继承链上都没有找到这个方法时会去找当前对象的 method_missing 方法它就好像每个无家可归的人最终的点method_missing是BasicObject中定义的私有实例方法所以每个子类都可以使用这个方法而BasicObject中是直接抛出这个异常所以需要我们自己去重写。method_missing也叫做幽灵方法。 现在我们通过method_missing来重构我们的Computer类 class Computerdef initialize(computer_id,data_source)id computer_iddata_source data_sourceend# name 为 调用的方法名args 表示参数 *表示接受所有的参数,封装为一个数组def method_miss(name,*args)# 判断data_source是否有这个方法如果没有则调用super.method_miss,也就是未找到该方法super if !data_source.respond_to?(get_#{name}_info)# 如果有这个方法info data_source.send(get_#{name}_info,id)price data_source.send(get_#{name}_price,id)result #{name}: #{info} : (#{price})result * result if price 99return resultend
end 现在发现我们不需要再定义额外的方法了直接通过幽灵方法来做判断与返回这里方法respond_to?表示该实例是否有目标方法如果返回值为Boolean一般会在方法上使用表示。 这里还有一个函数respond_to_missing? 会判断目标方法是否为幽灵方法。 在每次覆写respond_to?时都应该同时覆写respond_to_missing?方法
const_missing方法
如果对于一个常量的引用发现找不到则会默认调用const_missing方法将常量名作为一个符号进行传递。
class Moduledef const_missing(const_name)case const_namewhen :Taskp 我们已经更新了请访问 Rake::Taskwhen :FileTaskp 我们已经更新了请访问 Rake::FileTaskendend
end
# main是Object的实例Object是Class的实例Class的父类是Module所以当我们使用猴子补丁修改
# Module#const_missing时main对象是继承到这个方法的。
# 我们想要访问Task常量发现并不存在则触发了const_missing(const_name)
task_class Task # 我们已经更新了请访问 Rake::Task 在Rake中就有使用为了兼容老版本的Task和新版本的Rake::Task。
method_missing隐藏Bug 我们设计一个按照人名抽号码的小程序 class Roulettedef method_missing(name,*args)person name.to_s.capitalize3.times do# 这里在块儿内定义了numbernumber rand(10)1puts #{number}...end# 这里又使用了number因为作用域的不同运行时找不到这个变量所以默认会找 number这个方法因为也没有这个方法所以调用了method_missing方法导致不断的重入#{name} got #{number}end
end
number_of Roulette.new
p number_of.bob
p number_of.Jack 不知道你是否可以看出来当程序运行时会不断方法重入直到栈溢出。所以我们需要进行改良。
class Roulettedef method_missing(name,*args)person name.to_s.capitalize# 判断是否名字是否存在如果不存在直接报错super unless %w[Bob Frank Bill].include? personnumber 03.times donumber rand(10)1puts #{number}...end#{name} got #{number}end
end
number_of Roulette.new
p number_of.Bob
p number_of.Frank 白板类 比如说上面的 number_of.display 我们希望实际调用 method_missing 方法但实际上可能调用了Object.display方法这是因为我们从Object类中继承了大量的方法所以时常导致幽灵方法与继承方法的重复。 如果实例存在继承方法则幽灵方法是失效的。我们有两个办法
删除继承来的方法写一个白板类也就是很干净了的类
BasicObject
BasicObject是Object的父类其中定义的实例方法很少所以我们可以让现有的类继承BasicObject从而可以避免继承Object类的方法这是最简单的白板类实现方法
# [:__send__, :!, :instance_eval, :, :instance_exec, :!, :equal?, :__id__]
p BasicObject.instance_methods删除方法
Module#undef_method 删除所有的方法包括继承的Module#remove_method 只删除接受者自己的方法
所以我们最终可以选择让Roulette 继承 BasicObject或者删除指定方法
对比动态方法与幽灵方法
幽灵方法更容易出现隐性Bug所以能使用动态方法尽量使用动态方法除非不得不使用时才去使用记住如果重写 response_to? 也要重新 response_to_missing?
代码块
def my_method# 这里是my_method的作用域x Hiyield World
end
# 这里是顶级上下文的作用域
x Hello
my_method do |variable|# 这里Block内绑定的 x 是当前对象self的 x 和被传入的方法 my_method 内定义的x没有关系puts #{x} #{variable} # Hello World
end上面我们定义了一个Block并传入给my_method这里我们发现Block绑定了一个x变量x变量属于外部定义的my_method内部定义的x对于块儿内是不可见的。
def my_methodyield
end
num 1
my_method donum 1arg 2
end
p num # 这里正常输出 2
p arg # 这里无法输出引入Block内定义的变量作用域仅局限在内部外部不可见所以Block也叫做闭包
作用域
不论是JavaPythonRuby都会有作用域的概念就好像是单独的一个作用空间一个领地在这里有专属的局部变量
切换作用域
v1 1
class MyClassv2 2p local_variables # [:v2]def my_methodv3 3p local_variables # [:v3]endp local_variables # [:v2]
end
obj MyClass.new
obj.my_method
obj.my_method
p local_variables # [:v1, :obj]最初在顶层作用域 定义 v1 1
定义class MyClass切换作用域一旦切换作用域绑定也会修改v1对于MyClass内部域是不可见的在其内部定义了一个方法和v2因为定义的函数并未执行所以并未切换到新的域中。
当MyClass定义完毕再次切换回顶级作用域。 全局变量与顶级实例变量 $var 1
class MyClassdef incre$var 1end
end
p $var
obj MyClass.new
obj.incre
p $var这里定义了全局变量var发现在所有作用域都可以访问并操作到所以一旦出现问题很难排查。
var 1
def my_methodvar 1
end
p var
my_method
p var这里定义一个顶级上下文中的实例变量当main对象扮演self的角色就可以访问到顶级实例变量但如果进入其他对象作为self则无法访问到
var 1
class MyClassdef my_methodvar this is not top level varend
end
obj MyClass.new
p obj.my_method顶级实例变量 要比 全局变量 有限的安全
这里我们想弄清楚作用域是如何切换绑定是如何切换的需要了解作用域门
作用域门
程序一般会在三个地方关闭之前的作用域打开新的作用域分别为
方法 def类定义 class模块儿定义 module
每个关键字对应一个作用域门 现在看到每个作用域有独立的空间如果想要变量穿越作用域该如何操作 扁平化作用域
var Success
class MyClass# 这里想访问到vardef my_method# 这里想访问到varend
end一旦切换作用域局部变量就会失效如何能让var穿越两个作用域被访问到 Ruby是非常灵活的它为一种实现提供了多种方法上面知道了class module def三个关键字为作用域门那我们使用其他方式来实现相同的结果这样就可以避免切换作用域了
var Success
MyClass Class.new do # 这里想访问到varp vardefine_method :my_method do# 这里想访问到varp varend
end
obg MyClass.new
obg.my_method 如果两个作用域挤压在一起我们通常简称为 扁平作用域
共享作用域 如果想在一组方法之间共享一个变量但又不想别的方法访问到这个变量就可以把这些方法定义在该变量所在的扁平作用域 def my_methodshare 0# 使用内核方法调用 define_method 来定义函数而又不用切换域Kernel.send :define_method,:counter doshareendKernel.send :define_method,:inc do |x|share xend
end
my_method
p counter
inc 4
p counter上下文探针instance_eval
这里我们学习一个新的方法instance_eval,它能打开接受者的作用域并对其进行操作
class MyClassdef initializev 1end
end
obj MyClass.new
obj.instance_eval dop selfp v
end
# 这里 下面三行代码都处在扁平作用域所以Block可以使用局部变量v并访问到obj中的实例变量
v 2
obj.instance_eval {v v}
obj.instance_eval {p v}这里需要注意一个点instance_eval会将接收者变为当前对象self。而调用者的实例实例变量就落在作用域范围外如果不了解就会出现Bug例如 class Cdef initializex 1end
end
class Ddef twisted_methody 2# 在执行下面代码之前self为D.new一旦执行下面的代码C.new为selfx在C.new中定义了y则没有C.new.instance_eval {x : #{x} , y : #{y}}end
end
p D.new.twisted_method # x : 1 , y : 这里输出发现y并未访问到 可是上面的调用代码在同一个扁平作用域原来是因为instance_eval将C.new对象变为当前对象self调用者的实例变量就落在了作用域外了所以访问不到为nil该如何操作呢
instance_exec
class Ddef twisted_methody 2# 这里主动将 y传递到块儿中C.new.instance_exec(y) {|y|x : #{x} , y : #{y}}end
end洁净室
有时我们只想创建一个执行块儿的对象这样的对象称为洁净室洁净室最好继承BasicObject变为白板类这样就会以为内部集成的方法或常量而影响块儿的执行
class CleanRoomdef current_temperatureend
end
obj CleanRoom.new
obj.instance_eval doif current_temperature 20 # 执行方法end
end可调用对象
目前我们使用的Block是直接执行的我们需要让Block变为对象可以进行打包传递调用我们看一下有哪些打包代码的方式。
proc , 将Block转为Proc对象lambda 属于proc的变种使用方法
Proc对象
# 将Block打包为Proc
inc Proc.new {|x| x 1}
p inc.call 2inc_ proc {|x| x 1}
p inc.call 2obj lambda {|x| x 1}
p obj.call 2obj_ -(x) {x 1}
p obj_.call 2操作符
在调用方法时我们为其传递一个Block可通过yield进行执行但是如果我们想将这个Block封装起来延迟调用该如何操作
# 这里block将Block封装为Proc
def my_method(name,block)p name# 这里对其调用block.call
end
my_method qsc do p Hello World end如果想把 Proc 再转为 Block 该怎么操作 def my_method(greeting)p #{greeting} , #{yield}}
end
my_proc proc {Bill}
my_method(Hello,my_proc) 现在就可以将Block与Proc相互转化了
Lambda 和 Proc的区别
参数校验不同return定义不同
参数校验是指Lambda中定义两个入参如果你没传递或者传递多了则会报错如果是Proc定义两个入参如果没传递则变量为nil如果传递多了多余部分也不会使用。Lambda更严格一些
return定义不同Lambda中使用return表示从Lambda表达式中返回而Proc表示从定义Proc的作用域中返回
def my_methodp Proc.new {return 10}result p.call # 这里调用完就退出定义p的作用域所以下面执行不到return result * 2 # 这里实际上是不可到达的
endp my_method # 10 Metthod对象
class MyClassdef initializev 100enddef my_methodvend
end
obj MyClass.new
# 通过Kernel#method方法
mobj obj.method :my_methodp mobj.class # Method
p mobj.call
mobj.to_proc # 将Method转为ProcMethod和Proc有什么区别 lambda在定义它的作用域执行
Method对象会在自身所在的对象的作用域执行
自由方法
听名字感觉是一个脱离类模块儿的一个方法可以使用Module#unbind将一个方法转为自由方法也可以使用Module#instance_method获取一个自由方法
module MyModuledef my_method42end
end
unbound MyModule.instance_method(:my_method)
p unbound.class # UnboundMethod 自由方法并不能脱离对象执行所以我们可以把他再绑定到一个对象中使之再次成为Method对象可以使用UnboundMethod#bind进行绑定从某个类中分离出来的UnboundMethod只能绑定在该类或者子类的对象上模块儿中分离的自由方法则可以自由处置。
module MyModuledef my_method42end
end
unbound MyModule.instance_method(:my_method)
p unbound.class # UnboundMethodString.send :define_method,:another_method,unbound
p abc.another_method类定义
result class MyClassself
end
p result # MyClass 定义类或模块儿时内本身充当当前对象self的角色因为类和模块儿也是对象所以可以充当self这里我们引入一个相关的概念当前类
当前类 至今为止有几个概念混杂在一起当前对象当前类当前作用域 如果程序在哪个位置都会有一个当前对象self同样也总是有一个当前类或模块儿的存在定义一个方法时这个方法将成为当前类的一个实例方法。
self可以获取当前对象但是Ruby中并没有相应的方法获取当前类的引用我们这里有几个规则
在程序的顶层当前类为Object这是main对象所属的类(这就是在顶层定义方法会成为Object实例方法的原因)
# 这里定义的是private实例方法当前类为Object所以子类也会继承到这个方法
def say_hellop Hello World
endclass Userdef hisay_helloend
endobj User.new
# 这里是可以调用成功的
obj.hi
say_hello
obj.send :say_hello
# 这里无法调用
obj.say_hello # 因为say_hello是一个私有方法在一个方法中当前类就是当前对象的类比如我们在一个函数中定义另一个函数这个内部定义的函数属于当前对象的类
class User# 这里一旦执行当前类为Userdef one # 这里定义的函数生效并属于Userdef twoendend
end
obj User.new
obj.one
p User.instance_methods(false) # [:one, :two]当使用class或者module打开一个类时这个类成为当前类 如果我们想将类为参数动态的给类添加一个实例方法我们该如何操作 def add_method_to(a_class)# TODO : 在 a_class上定义方法 m()
end这里我们引入class_eval方法
class_eval方法
Module#class_eval方法会在一个已存在类的上下文中执行一个块儿。这听起来和obj.instance_eval很像。
def add_method_to(a_class)a_class.class_eval dodef m; Hello; endend
end
add_method_to String
abc.mModule#class_eval 会同时修改self和当前类所以可以定义方法 Object#instance_eval 只修改self
Module#class_eval功能和class类似但更强大因为class关键字传入 常量而Module#class_eval只要是类即可使用。
class也是作用域门会切换作用域而Module#class_eval则是扁平作用域可以引入外部变量 Module#class_eval 也有 class_exec 可以接收额外的代码块作为参数 类实例变量 这里需要声明类实例变量 和 类实例化对象的实例变量是不同的 class MyClass# 这里当前类为MyClassself也为MyClass这里定义my_var实例变量所属MyClass的my_var 100# 定义MyClass的read方法一个指向MyClass的类方法访问my_var 是可以的def self.read; my_var; end# 定义MyClass的实例方法write这里的my_var 和 外面的my_var 并不是一个变量作用域不同def write; my_var 2; end# 这里一样是访问不到外部的my_var,除非调用write方法给类的对象创建一个my_vardef read; my_var; end
end
obj MyClass.new
p obj.read # nil
obj.write # 定义 my_var 2
p obj.read # my_var 2
p MyClass.read # my_var 100 Ruby解释器假定所有的实例变量都属于当前对象self在类定义时也如此。 一个类实例变量只可以被类本身所访问而不能被类的实例或子类所访问到 类变量
如果想在类中定义变量可被子类或者实例对象访问到可以使用类变量它更像是Java中的静态变量.
class Cvar 1
end
class D Cdef hivarend
end
obj D.new
p obj.hi需要注意一点盲目的使用类变量也会有问题 不允许在顶级上下文中定义类变量因为main对象所属Object类定义类变量则所有Object子类都会继承这个类变量也就有修改类变量的可能在最新的Ruby编译器中已经对这个行为禁止并爆出错误
var 1
class Uservar 2
end
p var这里再回顾一下Ruby中的操作符 p false || true # 一个为真则为真
p false true # 一个为假都为假
p nil || a # 除了 nil 和 false其他都为真
p a || nil # || 遇到真则返回
p a || bp nil a # 遇到假则返回
p a nil
p a b类对象是否可以访问到类实例变量 class MyClass# 这里var 属于 MyClass因为MyClass也是一个对象var 1def get# 这里访问self为类的对象作用域分离varenddef self.getvarend
end
p MyClass.new.get # 无法访问到
p MyClass.get # 正常输出 1obj.instance.eval 改变obj为self如果在Block内定义实例变量则该实例变量属于obj
Class.class_eval 改变Class为self同时改变当前类定义实例变量属于这个类
class MyClassdef self.getvar enddef getvarend
endMyClass.class_eval do var 1
endp MyClass.get # 1
p MyClass.new.get # nil单件方法 我们现在想要修改一个类的实例方法有三种办法 class MyClass
end
# 1.0 猴子补丁
class MyClassdef oneend
end
# 2.0 细化
module MyClass_Plusrefine MyClass dodef twoendend
end
# 3.0 单件方法
obj MyClass.new
def obj.tree# 方法体
end
obj.tree单件方法我们可以看到是在对象上操作 定义的函数也只针对这个对象其他对象并没有这个方法所以叫做单件方法
类方法的真相
类方法的实质是一个类的单件方法因为类也是一个对象给类定义单件方法就是类方法。
类宏
Ruby中的对象是没有属性的对外只提供方法。所以在最初我们访问对象的实例变量时可以写getset方法但是这会很麻烦所以我们使用Module#attr_accessor :var访问器这也叫做类宏
单件类 提问单件方法类方法的信息是保存在哪里 首先不在对象中因为只有类和模块儿可以定义方法
其次也不在类中因为无法在类的对象中共享它们就好像是一个独立个体存在与某个与当前类有关的地方这个地方就是单件类负责存储单件方法。 那我们该如何访问到单间类内如何看到它 两种方式
class MyClass
end
obj MyClass.new
single_class class obj# 返回单件类self
end
p single_class # #Class:#MyClass:0x0000000108beb5c8
p single_class.class # Classclass MyClass
end
obj MyClass.new
# #Class:#MyClass:0x00000001051f3a78
p obj.singleton_class
other MyClass.new
# #Class:#MyClass:0x00000001051f3578
p other.singleton_class 单件类只有一个实例且无法被继承单件方法就定义在单件类中
单件类 和 方法查找 单件类的超类是什么 class MyClass
end
obj MyClass.new
# #Class:#MyClass:0x00000001051f3a78
p obj.singleton_class
# 对象的单件类的超类 就是 对象的所属类
p obj.singleton_class.superclass # MyClass单件类是否在祖先链中因为这涉及到方法的查找 单件类是存在于祖先链中的而且单件类的超类为对象的所属类所以在祖先链中排在当前类之前。方法查找也是按照这个顺序进行查找的。所以对象访问方法时是先在单件类中访问然后再去当前类中访问。 单件类的超类就是超类的单件类 class D
end
class E D
endp D.singleton_class # #Class:D
p E.singleton_class # #Class:E
p D.singleton_class.superclass # #Class:Object
p E.singleton_class.superclass # #Class:D上面的定义看起来有点儿绕Ruby为何这样设计 因为这样就可以在子类中调用父类的类方法
七条规则
对象要么是普通对象要么是模块儿模块可以是普通模块一个类或一个单件类方法存在与一个模块中通常定义在类中对象都有自己真正的类要么是普通类要么是单件类除了BasicObject没有超类其他的类都有一个祖先一个对象的单件类的超类就是这对象的类一个类的单件类的超类就是这个类的超类的单件类调用一个方法时Ruby先找到接收者真的类再向上进入祖先链
类方法的语法
class MyClass
end
# 1
def MyClass.one ; end
# 2
class MyClassdef self.two ; end
end
# 3
class MyClassclass selfdef three ;endend
end单件类 和 instance_eval方法
之前我们说instance_eval修改self实际上也修改当前类为接收者的单件类。
s1 abcs1.instance_eval dodef swoosh!reverseend
endp s1.swoosh! # cba
s2 qsc
p s2.respond_to?(:swoosh!) # false类扩展 我们之前再module中定义方法在类中include和pretend将模块儿方法转为类的实例方法那我们是否可以将module中的方法转为类的类方法 module MyModuledef my_method()p Hello Worldend
end
class MyClass# 在单件类中引入moduleself所指MyClass所以继承的方法为MyClass的类方法class selfinclude MyModuleend
end
MyClass.my_method # Hello World 这称为类扩展
对象扩展
module MyModuledef my_method()p Hello Worldend
end
class MyClass
end
obj MyClass.new
class obj# 单件类所属obj所以引入的方法会作为对象的单件方法include MyModule
end
obj.my_method 这称为对象扩展 类扩展对象扩展 因为用的很多所以Ruby提供了Object#extend方法 module MyModuledef my_method()p Hello Worldend
end
class MyClassextend MyModule
end
MyClass.my_methodobj MyClass.new
obj.extend MyModule
obj.my_method方法包装器 如何在原有函数不修改的前提下对方法做增强在此之前我们介绍一些新的东西 方法别名
alias_method :new_method_name , :old_method_name 对方法起一个别名
class MyClassdef onep Hello oneendalias_method :two,:one
endobj MyClass.new
obj.one
obj.two定义方法时并不是修改这个方法而是定义一个新的方法并将之前存在的方法名从新绑定只要老方法还存在一个绑定就仍可调用环绕别名
给方法定义一个别名重定义这个方法新方法中调用老的方法
class MyClassdef one p Hello oneendalias_method :two,:onedef onetwop Hello I am new oneend
endobj MyClass.new
obj.one
obj.two更多方法包装器
细化使用细化可以从新定义方法如果定义重名方法使用super则可调用到原先的内容Module#prepend因为会将引入module放入当前类的祖父链位置的前面所以也会覆盖掉当前类中定义的方法使用super则可调用到原先的内容
测试打破数据规律 让 1 1 3 绝大部分Ruby操作符实际上是方法 例如整数的只是名为Fixnum#方法的语法糖编写11时。实际上为1.(1)。
class Fixnumalias_method :old_plus , :def (value)self.old_plus(value).old_plus(1)end
end
p 11务必要慎用这种能力
编写代码的代码
Kernel#eval
前面我们学习了instance_eval , class_eval,现在我要学习Kernel#eval方法它的作用是执行一段代码字符串
# 执行 p 123 这段字符串p 123表示输出 123
eval p 123 代码字符串也可以访问局部变量
var 1
eval p varBinding 绑定对象
Binding是用一个对象表示完整的作用域可以使用eval方法在这个Binding对象所携带的作用域中执行代码Kernel#binding方法可以用来常见Binding对象
class MyClassdef my_thodx 1# 返回bindingbindingend
end
obj MyClass.new.my_thod 可以把Binding对象看作是一个闭包它只包含作用域而不包含代码对于eval方法可以传递一个Binding对象作为额外参数代码可以在这个Binding对象所携带的作用域中执行
eval p x , obj Ruby还定义了一个预定义常量TOPLEVEL_BINDING,它表示顶级作用域的Binding对象。可以在程序任务地方访问到。
eval self,TOPLEVEL_BINDING 其中在我们最早使用Ruby时都使用过irb,其实就是解析控制台或者文件输入再把每一行代码传递给eval方法执行这种类型的程序有时被称为代码处理器
eval方法后面三个为可选参数statement代表执行语句binding表示所在作用域file表示文件line表示执行行号,这对于查找问题时比较方便
eval(statement,binding,file,line)对比代码字符串 与 块
eval 只能执行代码字符串
Instance_evalclass_eval 可以执行Block也能执行代码字符串
一般能使用Block就使用Block
eval麻烦
不能利用编辑器的功能特性例如高亮难以阅读与修改错误隐藏到执行期安全性
代码注入
这里用Java操作SQL时遇到的SQL注入问题为例就是我们在执行SQL前如果SQL使用字符串拼接如果用户传递恶意参数就会导致SQL注入问题代码注入也是类似的。
防止代码注入
有些人会禁用eval方法毕竟可以找到替换的方式Ruby也提供了更安全的方法
污染对象和安全级别
Ruby会自动把不安全对象标记为污染对象比如Web表单文件命令行读取可以通过obj.tainted? 来判断
安全级别
可以通过给$SAFE全局变量赋值来实现一共有四个级别
0 随意操作
1 拒绝执行污染字符串
2 禁止绝大多数与文件相关的操作
3 每个创建对象都为被污染
为了谨慎使用安全级别可以为eval方法创建一个可控环境也称为沙盒比如在一个块儿内执行eval方法。
测试
写一个attr_accessor类宏类似的方法attr_checked与访问器类似但是会对属性进行校验attr_checked 可以接受属性名和代码块代码块用来进行校验如果是对属性赋值判断Block中是否为true如果为false则报错。需求通过一组代码进行展示
require test/unit
class Person;end
class TestCheckedAttribute Test::Unit::TestCasedef setupadd_checked_attribute(Person,:age)bob Person.newenddef test_accept_vaild_valuesbob.age 20assert_equal 20,bob.ageenddef test_refuses_nil_valuesassert_raises RuntimeError,Invalid attribute dobob.age nilendenddef test_refuses_false_valuesassert_raises RuntimeError,Invalid attribute dobob.age falseendend
end
# 这里是我们负责编写的代码 klass表示一个类对象attribute表示需要给该类加入属性
def add_checked_attribute(klass,attribute)
end使用eval进行快速的单元测试
# 先定义一个函数利用eval方法执行字符串代码
def add_checked_attribute(klass,attribute)eval # 打开类class #{klass}# 根据参数生成getset方法def #{attribute}(value)raise Invalid attribute unless value#{attribute} valueenddef #{attribute}()#{attribute}endend
end
# 这里对String添加属性my_attr
add_checked_attribute(String,:my_attr)
obj a
obj.my_attr 123
p obj.my_attr重构add_checked_attribute方法把eval方法使用Ruby方法替换掉
def add_checked_attribute(klass,attribute)# 这里使用class_eval打开类的作用域替换eval因为替换后无法使用# class因为class关键字无法接受使用参数作为类名klass.class_eval do# 使用扁平作用域使用define_method 替换 def因为这里方法名也是动态的define_method #{attribute} do |value|raise Invalid attribute unless value# 这里通过Object#instance_variable_* 方法来操作实例比变量instance_variable_set(#{attribute},value)enddefine_method #{attribute} doinstance_variable_get(#{attribute})endend
end
add_checked_attribute(String,:my_attr)
obj a
obj.my_attr 123
p obj.my_attr完成通过一个Block来校验属性
def add_checked_attribute(klass,attribute,validation)klass.class_eval dodefine_method #{attribute}} do |value|raise Invalid attribute unless validation.call(value)instance_variable_set #{attribute},valueenddefine_method #{attribute} doinstance_variable_get #{attribute}endend
end内核方法改造成一个类宏让它对所有的类定义中都可用我们试着在class或module中定义并且我们不需要再指类对象通过self即可读取到
class Class# 这样所有对象都有拥有这个方法了def attr_checked(attribute,validation)define_method #{attribute} do |value|raise Invalid attribute unless validation.call(value)instance_variable_set(#{attribute},value)enddefine_method #{attribute} instance_variable_get(#{attribute})endend
end钩子方法
也就是在代码运行中有各种事件我们可以利用事件进行操作这个就叫做钩子方法
类被继承时触犯
class String# inherited方法为Class的实例方法我们可以进行覆写操作def self.inherited(subclass)p #{self} was inherited by #{subclass}end
end模块儿被引入
module M1def self.included(othermod)p M1 was included into #{othermod}end
end
module M2def self.prepended(othermod)p M2 was prepended to #{othermod}end
end
class Cinclude M1prepend M2
end模块儿新增方法事件
module Mdef self.method_added(method)p New method: M##{method}enddef my_method;end
end我们现在需要保证引入CheckedAttributes的类才运行使用attr_checked module CheckedAttributes# 当类include CheckedAttributes 时被触发def self.included(base)# 将类extend ClassMethods也就是转为base类的类方法base.extend ClassMethodsendmodule ClassMethodsdef attr_checked(attribute,validation)define_method #{attribute} do |value|raise Invalid attribute unless validation.call(value)instance_variable_set(#{attribute},value)enddefine_method #{attribute} instance_variable_get(#{attribute})endendend
end小结
这里我们编写了自己的类宏并使用了钩子方法。我们已经可以所使用的对象为所欲为剩下的就交给我们不断的实践。后面我们将开启Rails之旅。 想学习编程的小伙伴十分推荐一个博主觉得很好的编程导航学习圈子 https://yupi.icu。里面有很多工作中的大佬不管是初学者还是已经工作的同学都能收获满满。加油
文章转载自: http://www.morning.mdwlg.cn.gov.cn.mdwlg.cn http://www.morning.hxbps.cn.gov.cn.hxbps.cn http://www.morning.wtwhj.cn.gov.cn.wtwhj.cn http://www.morning.rdnkx.cn.gov.cn.rdnkx.cn http://www.morning.wmhlz.cn.gov.cn.wmhlz.cn http://www.morning.wdnkp.cn.gov.cn.wdnkp.cn http://www.morning.zdbfl.cn.gov.cn.zdbfl.cn http://www.morning.xclgf.cn.gov.cn.xclgf.cn http://www.morning.tdldh.cn.gov.cn.tdldh.cn http://www.morning.thnpj.cn.gov.cn.thnpj.cn http://www.morning.vjwkb.cn.gov.cn.vjwkb.cn http://www.morning.zkqwk.cn.gov.cn.zkqwk.cn http://www.morning.mmxnb.cn.gov.cn.mmxnb.cn http://www.morning.hncrc.cn.gov.cn.hncrc.cn http://www.morning.fqqcn.cn.gov.cn.fqqcn.cn http://www.morning.rwbx.cn.gov.cn.rwbx.cn http://www.morning.wmyqw.com.gov.cn.wmyqw.com http://www.morning.tmlhh.cn.gov.cn.tmlhh.cn http://www.morning.btqrz.cn.gov.cn.btqrz.cn http://www.morning.hmlpn.cn.gov.cn.hmlpn.cn http://www.morning.mnccq.cn.gov.cn.mnccq.cn http://www.morning.blqgc.cn.gov.cn.blqgc.cn http://www.morning.zlnmm.cn.gov.cn.zlnmm.cn http://www.morning.gmztd.cn.gov.cn.gmztd.cn http://www.morning.dwzwm.cn.gov.cn.dwzwm.cn http://www.morning.qcslh.cn.gov.cn.qcslh.cn http://www.morning.ummpdl.cn.gov.cn.ummpdl.cn http://www.morning.nxzsd.cn.gov.cn.nxzsd.cn http://www.morning.btypn.cn.gov.cn.btypn.cn http://www.morning.dzrcj.cn.gov.cn.dzrcj.cn http://www.morning.djpps.cn.gov.cn.djpps.cn http://www.morning.fbzyc.cn.gov.cn.fbzyc.cn http://www.morning.pkmcr.cn.gov.cn.pkmcr.cn http://www.morning.hbxnb.cn.gov.cn.hbxnb.cn http://www.morning.tnktt.cn.gov.cn.tnktt.cn http://www.morning.yymlk.cn.gov.cn.yymlk.cn http://www.morning.snrhg.cn.gov.cn.snrhg.cn http://www.morning.nqlx.cn.gov.cn.nqlx.cn http://www.morning.fhqsm.cn.gov.cn.fhqsm.cn http://www.morning.nkqrq.cn.gov.cn.nkqrq.cn http://www.morning.qlrwf.cn.gov.cn.qlrwf.cn http://www.morning.ydnx.cn.gov.cn.ydnx.cn http://www.morning.rwxnn.cn.gov.cn.rwxnn.cn http://www.morning.jkpnm.cn.gov.cn.jkpnm.cn http://www.morning.ydfr.cn.gov.cn.ydfr.cn http://www.morning.ksggr.cn.gov.cn.ksggr.cn http://www.morning.rwfp.cn.gov.cn.rwfp.cn http://www.morning.nfbkz.cn.gov.cn.nfbkz.cn http://www.morning.fkcjs.cn.gov.cn.fkcjs.cn http://www.morning.cxlys.cn.gov.cn.cxlys.cn http://www.morning.wxqmc.cn.gov.cn.wxqmc.cn http://www.morning.njnqn.cn.gov.cn.njnqn.cn http://www.morning.lwdzt.cn.gov.cn.lwdzt.cn http://www.morning.zrgsg.cn.gov.cn.zrgsg.cn http://www.morning.glnmm.cn.gov.cn.glnmm.cn http://www.morning.srtw.cn.gov.cn.srtw.cn http://www.morning.jbtwq.cn.gov.cn.jbtwq.cn http://www.morning.kmbgl.cn.gov.cn.kmbgl.cn http://www.morning.nkjkh.cn.gov.cn.nkjkh.cn http://www.morning.gtylt.cn.gov.cn.gtylt.cn http://www.morning.slysg.cn.gov.cn.slysg.cn http://www.morning.wnnlr.cn.gov.cn.wnnlr.cn http://www.morning.zzaxr.cn.gov.cn.zzaxr.cn http://www.morning.iznek.com.gov.cn.iznek.com http://www.morning.sjwiki.com.gov.cn.sjwiki.com http://www.morning.tkxr.cn.gov.cn.tkxr.cn http://www.morning.fmqng.cn.gov.cn.fmqng.cn http://www.morning.gxcym.cn.gov.cn.gxcym.cn http://www.morning.xjnjb.cn.gov.cn.xjnjb.cn http://www.morning.lcbgf.cn.gov.cn.lcbgf.cn http://www.morning.nsyzm.cn.gov.cn.nsyzm.cn http://www.morning.fpkdd.cn.gov.cn.fpkdd.cn http://www.morning.bpmft.cn.gov.cn.bpmft.cn http://www.morning.crkmm.cn.gov.cn.crkmm.cn http://www.morning.nxbsq.cn.gov.cn.nxbsq.cn http://www.morning.rcwzf.cn.gov.cn.rcwzf.cn http://www.morning.dwhnb.cn.gov.cn.dwhnb.cn http://www.morning.wfykn.cn.gov.cn.wfykn.cn http://www.morning.mqbdb.cn.gov.cn.mqbdb.cn http://www.morning.rkck.cn.gov.cn.rkck.cn