ruby丰富多彩的高级功能

时间:2022-08-31 16:03:39

这一章节我用了两天,把手头的项目也给放了。终于在今天看完了,但仅限于看完,中间还有很多东东还得需要时间去消化。总结下这一章节中看过的内容,大致的包括:模块的定义与使用、其他文件类的导入、自定义异常的处理、Proc对象的使用、正则表达式、时间类的使用、线程的定义与使用多线程、垃圾回收机制的介绍。看起来的内容还挺多的,而且都比较重要,所以也不枉用了两天的时间来看,庆幸的是这节的内容之前也有所接触,所以看起来也不会那么的费劲,能够快速的理解

首先一个模块是以前没有见过的,模块使用module来定义,而且不能实例化,如果要用里面定义的实例方法,可以把模块放到一个类里,实例化这个类,然后再使用模块里面的实例方法,如下

module MyModule
  def showModule
    puts "这是定义在module里的实例方法"  
  end
  
  def self.showInfo
    puts "这是定义在module的类方法"
  end
end
#模块不能实例化,所以下面的是不存在的
#mm = MyModule.new
MyModule.showInfo
<pre name="code" class="ruby">这是定义在module的类方法
 

把模块放到类里面,称之为混含操作(感觉像混蛋操作),这样就要以使用模块里的实例方法了

puts "将模块放入类中,这样就可以调用模块中的实例方法了,称之为混含操作"
class ModuleInclude
  include MyModule
end
mi = ModuleInclude.new
mi.showModule
将模块放入类中,这样就可以调用模块中的实例方法了,称之为混含操作
这是定义在module里的实例方法
模块的另一个比较实用的方法就是实现了相当于命名空间的作用,也就是模块与模块之前是隔开的,不会相互影响

puts "module实现命名空间,同样的方法不会报错"
module FirstModule
  class FirstClass
    def show
      puts "FirstModule里FirstClass的show方法"
    end
  end
end

module SecondModule
  class SecondClass
    def show
      puts "SecondModule里SecondClass的show方法"
    end
  end
end
showMethod = FirstModule::FirstClass.new
showMethod.show
secondMethod = SecondModule::SecondClass.new
secondMethod.show
<pre name="code" class="ruby">module实现命名空间,同样的方法不会报错
FirstModule里FirstClass的show方法
SecondModule里SecondClass的show方法
 可以看到,在模块里有相同的sho方法,但不会报错,就算是相同的类名也不会报错,这个方法虽然在类里也可以实现,但模块应该对系统的开支更小 

在一个文件执行时,ruby也提供了类似于前置通知和后置通知的功能,使用BEGIN,END/at_exit。这些比较好理解,只是需要注意的是END的执行顺序要和代码的顺序相反的

puts "程序的执行顺序"
BEGIN{
  puts "程序初始化一"
}
BEGIN{
  puts "程序初始化二"
}
def info
  puts "程序运行,复制文件..."
end
info
at_exit{
  puts "program exit one"
}
at_exit{
  puts "program exit two"
}
END{
  puts "程序正在退出一"
}
END{
  puts "程序正在退出二"
}
程序初始化一
程序初始化二
...
程序正在退出二
程序正在退出一
program exit two
program exit one
可以看到,END和at_exit执行的顺序和代码顺序完全相反,而且不管BEGIN/END出现在文件的什么位置,都会是同样的效果

在实际的开发中,肯定不可能把所有的代码写在一个文件内,这时加载其他文件内的内容就显得非常重要了,加载其他文件有两种方式load/require,load可以加载多次,需要指定文件后缀,但require只能加载一次,否则会返回false,不需要指定文件后缀

puts "使用load加载文件资源"
load "demo/moduleY.rb"
require "#{File.dirname(__FILE__)}/demo/modulea"
mod
加载的moduleY.rb中

module ModuleName
  def show
    puts "a show method in ModuleName"
  end
  
  def self.say
    puts "a static say method in ModuleName"
  end
end
ModuleName.say
加载的modulea.rb中

def mod
  puts "hello, my inviewer!"
end
结果:
使用load加载文件资源
a static say method in ModuleName
hello, my inviewer!
如果只是引入一个module可以用include和extend。这就是上面讲的混含操作,include是指引用地址,extend是引入内容

ruby有一个我暂时认为比较无聊的功能-命名别名

puts "为方法定义别名"
def name
  puts "这是之前的name方法"
end
alias new_name name
def name
  puts "这是重定义name之后"
end
name 
new_name
$old = 3
alias $new $old
puts "命名别名,$old重定义之前",$old,$new
$old = 1
puts "命名别名,$old重定义之后",$old, $new
为方法定义别名
这是重定义name之后
这是之前的name方法
命名别名,$old重定义之前
3
3
命名别名,$old重定义之后
1
1
从上面可以看出来,当定义是不是全局变量的时候相当于把原来的内容放到new_name里面,我能想到的不就是用变量保存内容么?要搞个另外的功能吗?而且全局变量的时候别名和原名只能同时改变,真搞不懂这个的作用是什么

如果需要知道当前的这个变量是什么类型的可以使用defined?来判断,又一个体现动态言语的方法undef出现了,这个可以取消已定义的方法,如果再有调用的就会报错,这让我想到了可以利用这个方法来做黑客的入侵功能,嘿嘿~

class Replay
  def play
    puts "开始播放"
    puts defined? "真的开始了吗 ?"
  end
end
rp = Replay.new
puts defined? rp.play
rp.play
#释放定义后将会出错
#undef play
#puts defined?rp.play
method
开始播放
expression
puts defined?rp.play由于这个play是方法,所以输出的是method。后面的“真的开始了吗?”是一个表达式,所以输出了expression

和代码块差不多,让我感觉比较难的Proc对象,用了我大部分的时间,一看到这个我就不想再继续看下去,但没办法,只能逼着看完

Proc对象一个主要的功能就是把一些重复的东东放在一个块在,又没有作用域的限制,在其他地方都可以调用,估计在以后的开发过程中会大量使用

当一个Proc被new出来的时候,并不会马上执行,只有使用了call方法才会被执行,

puts "Proc对象的使用,使其局部变量对象化的过程"
pr = Proc.new{puts "初始化一个Proc对象"}
pr.call
def call_proc(pr)
  a = "我是在proc对象里的变量"
  puts a
  #调用Proc对象
  pr.call
end
a = "我是在外部声明的变量"
pr = Proc.new{puts a}
pr.call
call_proc(pr)
Proc对象的使用,使其局部变量对象化的过程
初始化一个Proc对象
我是在外部声明的变量
我是在proc对象里的变量
我是在外部声明的变量

调用看起来还是比较简单,而且很好理解,当在一个方法参数里添加&,程序则会把这个形参作为Proc对象来处理

def grab_blc(&blc)
  blc.call
end
grab_blc{puts "这是一个形参的proc对象"}
这是一个形参的proc对象
当需要一个Proc对象时,除了用new来初始化,还能用proc方法来生成

alue = proc do |value|
  puts "使用proc方法创建对象,传递的值是:#{value}"
end
Value.call("使用call传值")
def info
  yield "使用yield代码块传值"
end
info &Value
使用proc方法创建对象,传递的值是:使用call传值
使用proc方法创建对象,传递的值是:使用yield代码块传值
在实际使用中,比如一个实体类会被经常使用到,所以把这个实体类做为一个Proc对象

puts "使用proc来输入实体的属性"
def info(pr)
  pr.call
end
entity = Proc.new{
  @name = "wicky"
  @age = 23
  @gender = "male"
  @hobby = "programming"
  
  puts "name:#{@name}"
  puts "age:#{@age}"
  puts "gender:#{@gender}"
  puts "hobby:#{@hobby}"
}
info (entity)
使用proc来输入实体的属性
name:wicky
age:23
gender:male
hobby:programming
任何一门言语,异常处理机制是不可或缺的,因为程序写的不可能完美,ruby提供的异常处理和其面向对象语言差不多,而且定义的异常也简单的多,包括:RuntimeError/IOError/Errorno::error/NameError/NoMethodError/TypeError/ArgumengError,总共才7个,所以运用起来也方便很多,捕获的方法也很简单,相当于try(){}catcher的,begin/end块来包含可能出现异常的代码,使用rescue来声明捕获的异常,还提供了手动抛出异常的raise和重新运行异常代码块的retry,虽然ruby本身的异常处理机制已经很简单明了,但还是人性化的提供了自定义异常,和自定义异常提示方法。下面是一个异常的模拟

isError = true
begin
  puts "正在请求服务器,请稍后..."
  if isError
    raise "连接超时"
  end
  puts "请求成功"
  
  rescue
    isError = false
    puts "#{$!.to_s},重新请求"
    retry
end
正在请求服务器,请稍后...
连接超时,重新请求
正在请求服务器,请稍后...
请求成功
模拟的是连接服务器的过程,当第一次运行时,isError=true,会进入raise手动抛出异常,并且带有自定义的异常信息,这时程序跳到rescue处理异常,把isError=false模拟连接成功,并且打印出异常信息($!是全局保存异常信息的变量,$@保存全局异常信息的位置等),这时调用了retry重新连接服务器。关于自定义的处理也比较简单,无非就是自己定义一个类,然后继承下Exception就完事了,后面了处理只要把捕获到的异常raise成自己的异常类就行了

编程另外一个非常重要的知识点线程,这是任何程序猿都必须知道的事,ruby的线程操作非常简单,大大简化了线程的操作过程,而且提供了很多有用的库供使用

i = 1
thread = Thread.new do
  10.times { |count|
    puts "当前线程#{count}"
  }
end
thread2 = Thread.start do
  puts "这是线程2"
end
#不加入线程则,无法打印出信息
thread.join
thread2.join
当前线程0
当前线程1
当前线程2
当前线程3
当前线程4
当前线程5
当前线程6
当前线程7
当前线程8
当前线程9
这是线程2
不知道是不是版本的问题,我用的是1.9.3必须得把这个线程join到当前运行的线程中才可以打印信息,但有些版本好像可以不用join,真搞不懂
ruby提供了一个非常好的类来处理著名的售票问题

@num = 200
@mutex = Mutex.new
def ticketSeller(tickets)
  @mutex.lock
  Thread.pass
  if (@num >= tickets)
    @num -= tickets
    puts "您购买#{tickets}已成功"
  else
    puts "出票失败,没足够的票,当前余票#{@num}" 
  end
  @mutex.unlock
end
buyer1 = Thread.new(199){|num| ticketSeller(num)}
buyer2 = Thread.new(2){|num| ticketSeller(num)}
buyer1.join
buyer2.join
您购买199已成功
出票失败,没足够的票,当前余票1
Mutex提供了一个线程锁,能够把当前的这个块锁定,不让其他线程访问,这样就可以保证数据的正确性,直到调用unlock方法,另外线程还提供了pass/sleep/join/wakeup/current等显示/休眠/挂起/唤醒/当前线程方法,如果需要查询当前的线程状态可以用到status。拥有的值有:sleep/run/aborting/false/nil表示休眠、运行、被取消、正常终止、被非正常终止等。另一个比较常用的库SizedQueue也非常有用,能够让线程按顺序执行,而不用担心同步问题

在这节的学习中,是学习ruby为止比较重要而且比较难的部分,写的代码也越来越多,越来越复杂,一些常见的基础知识也基本用到了,接下来就得了解ruby的IO操作为对数据库的操作了,看完这两个比较重要的内容准备着手ROR,毕竟不断的挑战难点才能回首现在,让现在难以理解或使用的知识自动变的简单

KEEPPING UP