Ruby Study 8 : Methods

简介:
1. Class Methods
调用一个方法时, 可以通过定义这个方法的类的对象来调用, 那么这个称为实例方法.
还可以定义类方法, 这种方法的调用需要通过类来调用, 不能通过这个类的对象来调用.
其实我们创建的Class也是一个对象, 这个后面会讲到, 所以方法都是由对象来调用的.
class MyClass
   def MyClass.classMethod  # 定义class method可以使用这种写法, 后面还会介绍其他写法.class method也称为这个class的singleton method.
      puts( "This is a class method" )
   end
   def instanceMethod
      puts( "This is an instance method" )
   end
end
# 也可以把class method的定义写在class外面 : 
# def MyClass.classMethod
#   puts( "This is a class method" )
# end

mobj1 = MyClass.new()

MyClass.classMethod  #调用class method
mobj1.instanceMethod  #调用instance method

执行结果 :
This is a class method
This is an instance method

以下是错误的 : 
MyClass.instanceMethod        #=> Error! This is an 'undefined method'
ob.classMethod          #=> Error! This is an 'undefined method'

既然class method不能通过这个class的对象来调用, 那么它通常用在什么场合呢?
1.1. 还记得创建对象的时候用到的new方法吗, 它就是class method. 因为对象还没创建, 当然是没有办法调用instance method的. 所以这是一种使用情况.
1.2. 用在某些特殊的类中, 不需要创建对象反而使用更合理, 例如File类.
#!/opt/ruby/bin/ruby
fn = 'test.rb'
if File.exist?(fn) then
   puts(File.expand_path(fn))
   puts(File.basename(fn))
   puts(File.dirname(fn))
   puts(File.extname(fn))
   puts(File.mtime(fn))
   puts("#{File.size(fn)} bytes")
else
   puts( "Can't find file!")
end

执行结果 : 
/root/rubyscript/test.rb
test.rb
.
.rb
2012-03-06 12:10:44 +0800
265 bytes


2. Class Variables
前面的章节已经介绍了几种变量 : 全局变量, 本地变量, 实例变量, 类变量.
类变量以@@开头, 作用域是类以及它的对象. 需要声明的是, 类变量必须在创建类时被初始化(可以为nil).
因为所有的Class都是Class的对象, 所以类也可以有它的实例变量.
class Myclass
  @@c1 = nil  # 初始化时可以给nil
  # @@c1  # 已经讲过, 类变量使用前必须初始化, 所以注释掉的这行是有问题的.
  @i1
  def Myclass.get
    puts(@@c1)
  end
end

再来看一个例子, 可以更清晰的知道, 类的实例变量存储的值是在它本身的, 而不是它的对象. 它的对象的实例变量的值可以通过initialize方法来初始化.
class MyClass
    @@classvar = 1000
    @instvar = 1000
    def MyClass.classMethod
        if @instvar == nil then
            @instvar = 10
        else
            @instvar += 10
        end
        if @@classvar == nil then
            @@classvar = 10
        else
            @@classvar += 10
        end            
    end
    def instanceMethod
        if @instvar == nil then
            @instvar = 1
        else
            @instvar += 1
        end
        if @@classvar == nil then
            @@classvar = 1
            else
            @@classvar += 1
        end        
    end
    def showVars
        return "(instance method) @instvar = #{@instvar}, @@classvar = #{@@classvar}"
    end
    def MyClass.showVars
        return "(class method) @instvar = #{@instvar}, @@classvar = #{@@classvar}"
    end
end

以上代码执行完后这个MyClass就有了一个类变量和一个实例变量, 初始值都是1000.
其中类变量的作用域是它和它的对象. 而实例变量的作用域仅仅是它自己.
下面来操作一下这个类 :
for i in 0..2 do    
   ob = MyClass.new
   MyClass.classMethod
   ob.instanceMethod
   puts( MyClass.showVars )
   puts( ob.showVars )
end

执行结果 : 
(class method) @instvar = 1010, @@classvar = 1011
(instance method) @instvar = 1, @@classvar = 1011
(class method) @instvar = 1020, @@classvar = 1022
(instance method) @instvar = 1, @@classvar = 1022
(class method) @instvar = 1030, @@classvar = 1033
(instance method) @instvar = 1, @@classvar = 1033

从结果上印证了MyClass的实例变量@instvar是属于它自己的, 它的对象的@instvar没有初始值.
而类变量@@classvar是它和它的对象都可以访问的.
另外再来看一个例子 : 
使用 instance_variable_get这个singleton方法获取实例变量的值.
class MyClass
   @@classvar = 1000
   @instvar = 1000
   
   def MyClass.classMethod
      if @instvar == nil then
         @instvar = 10
      else
         @instvar += 10
      end
   end
   
   def instanceMethod
      if @instvar == nil then
         @instvar = 1
      else
         @instvar += 1
      end            
   end         
end
ob = MyClass.new
puts MyClass.instance_variable_get(:@instvar)
puts( '--------------' )
for i in 0..2 do   
   # MyClass.classMethod
   ob.instanceMethod  # 它改变的只是它的实例变量值
   puts( "MyClass @instvar=#{MyClass.instance_variable_get(:@instvar)}")
   puts( "ob @instvar= #{ob.instance_variable_get(:@instvar)}" )
end

执行结果 :
1000
--------------
MyClass @instvar=1000
ob @instvar= 1
MyClass @instvar=1000
ob @instvar= 2
MyClass @instvar=1000
ob @instvar= 3

# MyClass.classMethod这行的注释去掉, 重新执行 :
1000 -------------- MyClass @instvar=1010 ob @instvar= 1 MyClass @instvar=1020 ob @instvar= 2 MyClass @instvar=1030 ob @instvar= 3


3. A Class is an Class Object
前面已经多次提到任何类都是Class类的对象.
require "Set"
require "Matrix"
class Myclass
end
p Myclass.class
p Array.class
p Set.class
p Vector.class
p Hash.class
p String.class
p Fixnum.class
p Float.class

输出全是Class.

4. Ruby Constructors : new or initialize ?
Constructor用于给Class构造一个对象, 在之前我们接触过类中定义的initialize方法 . 
在创建一个对象时, 使用的是new方法, 而new方法会去查找这个类是否定义了initialize方法, 如果定义了initialize方法, 那么它会去调用initialize. 
同时, 传给new的参数值将传递给initialize方法. initialize是一个private方法, 不能通过对象直接调用, 需要通过它的方法来调用. 这也就是通过new来调用initialize方法的来由了. 后面会讲到方法的级别.
例如 :
class Myclass
  def initialize(a)
    @x = a
  end
end

mobj1 = Myclass.new()  

报错, 因为new方法调用initialize方法, 需要1个参数值传进去, 而在构造是没有传递进去. 所以报错了.
C:/Users/digoal/Desktop/new.rb:2:in `initialize': wrong number of arguments (0 for 1) (ArgumentError)
 from C:/Users/digoal/Desktop/new.rb:7:in `new'
 from C:/Users/digoal/Desktop/new.rb:7:in `<main>'


mobj1 = Myclass.new(nil)
mobj1.initialize('digoal')  # 这里报错, 原因是它去调用了private方法, private不允许直接通过对象调用, 只能通过方法调用
C:/Users/digoal/Desktop/new.rb:8:in `<main>': private method `initialize' called for #<Myclass:0x1dffdf0 @x=nil> (NoMethodError)

那么new方法能不能自定义呢, 最好不要这么做, 因为方法返回值是最后一行或显示的写明return. 来看一个例子 :
class MyClass
   def initialize( aStr )
      @avar = aStr
   end
   def MyClass.new( aStr ) 
      super  # 调用superclass的new方法.
      @anewvar = aStr.swapcase  # 这一行变成了new方法的返回值
   end
end
ob = MyClass.new( "hello world" )  # 所以ob变成了String类的对象
puts( ob )
puts( ob.class )

执行结果 : 
HELLO WORLD
String

那么把super放在new方法的最后一行, 再重试一下 :
class MyClass
   def initialize( aStr )
      @avar = aStr
   end
   def MyClass.new( aStr ) 
      @anewvar = aStr.swapcase
      super
   end
end
ob = MyClass.new( "hello world" )
puts( ob )
puts( ob.class )
puts(ob.instance_variable_get(:@avar))
p(ob.instance_variable_get(:@anewvar))

执行结果 : 
#<MyClass:0x1ee0e98>
MyClass
hello world
nil

显然在new方法中定义的@anewvar它的值并没有初始化到ob对象的@anewvar实例变量上.
那么它到哪里去了呢 ? 原来初始值在类里面 . 也就是说要初始值到类的对象里面, 我们必须在initialize中初始它.
puts(MyClass.instance_variable_get(:@anewvar))
执行结果 : 
HELLO WORLD


5. Singleton Methods
Singleton method从字面上理解就是对象专有的方法, 例如有Class对象专有的方法, 自定义类对象的专有方法等.
以下例子使用了三种方法定义Myclass的singleton method.
使用了两种方法定义ob对象的singleton method
class Myclass
  def initialize(a)
    @x = a
  end
  def m1
    puts("this is a Myclass's object's method.")
  end
  def Myclass.singleton_m1  # 第1种创建Myclass singleton method的方法.
    puts("this is a Myclass's singleton method.")
  end
  class << self  # 第2种创建Myclass singleton method的方法.匿名类
    def singleton_m2
      puts("this is another Myclass's singleton method.")
    end
  end
end
class << Myclass  # 第3种创建Myclass singleton method的方法, 这种方法可以写在class定义的外面. 匿名类
  def singleton_m3
    puts("this is another Myclass's singleton method.")
  end
end

ob = Myclass.new('digoal')
class << ob  # 第1种创建ob对象的singleton method的方法, 匿名类
  def ob_singleton_m1
    puts("this is a ob's singleton method.")
  end
end

def ob.ob_singleton_m2  # 第2种创建ob对象的singleton method的方法,
  puts("this is another ob's singleton method.")
end

p Myclass.singleton_methods  # 打印出属于Myclass的singleton methods
p ob.singleton_methods  # 打印出属于ob对象的singleton methods
ob.m1
ob.ob_singleton_m1
ob.ob_singleton_m2
Myclass.singleton_m1
Myclass.singleton_m2
Myclass.singleton_m3
ob1 = Myclass.new('DIGOAL')
ob1.m1
# 如果去掉以下注释, 以下报错, 因为它们调用了别的对象的singleton method.
=begin
ob1.ob_singleton_m1
Myclass.ob_singleton_m1
ob.singleton_m1
=end

执行结果 :
[:singleton_m1, :singleton_m2, :singleton_m3]
[:ob_singleton_m1, :ob_singleton_m2]
this is a Myclass's object's method.
this is a ob's singleton method.
this is another ob's singleton method.
this is a Myclass's singleton method.
this is another Myclass's singleton method.
this is another Myclass's singleton method.
this is a Myclass's object's method.

我们来看看系统内置的类IO和File的singleton methods : 
p File.singleton_methods.sort
p IO.singleton_methods.sort
[:absolute_path, :atime, :basename, :binread, :binwrite, :blockdev?, :chardev?, :chmod, :chown, :copy_stream, :ctime, :delete, :directory?, :dirname, :executable?, :executable_real?, :exist?, :exists?, :expand_path, :extname, :file?, :fnmatch, :fnmatch?, :for_fd, :foreach, :ftype, :grpowned?, :identical?, :join, :lchmod, :lchown, :link, :lstat, :mtime, :new, :open, :owned?, :path, :pipe, :pipe?, :popen, :read, :readable?, :readable_real?, :readlines, :readlink, :realdirpath, :realpath, :rename, :select, :setgid?, :setuid?, :size, :size?, :socket?, :split, :stat, :sticky?, :symlink, :symlink?, :sysopen, :truncate, :try_convert, :umask, :unlink, :utime, :world_readable?, :world_writable?, :writable?, :writable_real?, :write, :zero?]
[:binread, :binwrite, :copy_stream, :for_fd, :foreach, :new, :open, :pipe, :popen, :read, :readlines, :select, :sysopen, :try_convert, :write]

如何来判断一个对象是否有某个singleton method呢, 我们前面已经通过singleton_methods返回了一个Array类型的对象.
那么我们可以通过Array的方法来判断是否有某个元素, 这样来判断这个方法是否存在于你要找的对象中.
例如 :
p File.singleton_methods.include?(:new)
结果 : 
true

或者直接使用respond_to?方法 : 
p File.respond_to?(:new)
结果 : 
true

6. Ancestor Class
我们前面使用superclass方法可以找到调用这个方法的对象的父类, 例如 :
def showFamily( aClass )
    if (aClass != nil) then    
        puts( "#{aClass} :: about to recurse with aClass.superclass = #{aClass.superclass.inspect}" )
        showFamily( aClass.superclass )
    end
end

showFamily(Array)

执行结果 : 
Array :: about to recurse with aClass.superclass = Object
Object :: about to recurse with aClass.superclass = BasicObject
BasicObject :: about to recurse with aClass.superclass = nil


7. Singleton Classes
singleton method和singleton class其实是一个概念, 前者是类对象的特有方法, 后者是对象的特有方法.
前面已经举了几种创建方法 .
def Classname.methodname

class << self
  def methodname

class << Classname
  def methodname

def objectname.methodname

class << objectname
  def methodname


8. Overriding Methods
复写方法, 也多次列举了.
例如to_s方法, 顶级的to_s方法输出的是类名, 对象ID, 对象中的实例变量和它的值.
Array类复写了来自superclass的to_s方法, 我们来看个例子 : 
inspect方法会去调用to_s方法, 所以我们复写to_s可以看出区别.
class Myclass
  def initialize(a)
    @x = a
  end
end

mobj1 = Myclass.new('digoal')
print(mobj1.inspect,"\n")
# 下面复写to_s方法
class Myclass
  def to_s
    puts("this ia a overrided to_s method.")
  end
end
print(mobj1.inspect)

输出结果 :
#<Myclass:0x1de0f68 @x="digoal">
this ia a overrided to_s method.

再引用书上的一个例子 : 
class MyClass
   def sayHello
      return "Hello from MyClass"
   end
   
   def sayGoodbye
      return "Goodbye from MyClass" 
   end
end
class MyOtherClass < MyClass
    def sayHello            #overrides (and replaces) MyClass.sayHello
        return "Hello from MyOtherClass"
    end
        # overrides MyClass.sayGoodbye   but first calls that method 
        # with super. So this version "adds to" MyClass.sayGoodbye   
    def sayGoodbye   
        return super << " and also from MyOtherClass"
    end
        # overrides default to_s method
    def to_s
        return "I am an instance of the #{self.class} class"
    end
end



9. Public, Protected, Private Methods
Ruby定义了3种方法的访问级别,
public
protected
private
我们前面定义的方法默认都是public的, 没有访问限制. 注意 initialize方法是private的.
protected和private方法都只能通过其他方法间接的访问, protected方法可以在public方法中被直接调用, 或间接调用(使用它自己的对象或,其他带有这个protected方法的父类或子类的对象调用) 来看个例子 :
class MyClass
    private
        def priv
             puts( "private" )
        end
    protected
        def prot
             puts( "protected" )
        end
    public
        def pub
             puts( "public" )
        end
        def useOb( anOb )
             anOb.pub
             anOb.prot  # 调用它可以直接调用或通过同类的对象来调用,或具有prot方法的子类或父类来调用, 所以这里在后面的调用是可以的.
             anOb.priv  # 调用它的时候会报错, 因为private方法只能在public方法中直接调用.
        end
end

mobj1 = MyClass.new
mobj2 = MyClass.new
mobj1.pub
# mobj1.prot # 直接调用失败
# mobj1.priv # 直接调用失败
mobj1.useOb(mobj1)
mobj1.useOb(mobj2)

执行结果 :
C:/Users/digoal/Desktop/new.rb:17:in `useOb': private method `priv' called for #<MyClass:0x1dc0b08> (NoMethodError)
 from C:/Users/digoal/Desktop/new.rb:26:in `<main>'
public
public
protected

那么怎么来调用priv方法呢 :
class MyClass
    private
        def priv
             puts( "private" )
        end
    protected
        def prot
             puts( "protected" )
        end
    public
        def pub
             puts( "public" )
        end
        def pub1
          prot
          priv
        end
end

mobj1 = MyClass.new
mobj1.pub1

执行结果 : 
protected
private

看起来有点多此一举, 所谓想让他私有化当然就不想直接被调用.
下面来看一个级联的类, 它在访问私有方法的时候是否允许?
class Myclass
    private
        def priv
             puts( "private" )
        end
    protected
        def prot
             puts( "protected" )
        end
    public
        def pub
             puts( "public" )
        end
        def pub1
          pub
          prot
          priv
        end
end

class Myclass1 < Myclass
  private
    def priv  # 复写priv方法. 仍然可以被调用.
      puts( "private1")
    end
end

class Myclass2 < Myclass
end

执行结果 : 
public
protected
private1
public
protected
private

pub1 方法可以定义在Myclass, 也可以定义在Myclass1和Myclass2. 都可以达到同样的目的 . 
也就是说private方法只能在类定义的public方法中访问, 而不能通过对象直接访问.
protected方法则更加宽松, 可以通过public方法来直接调用或者间接调用(同类或具有这个protected方法的父类或子类的对象)
来看一个书上面的例子 :
class MyClass
    
    private
        def priv( aStr )            
            return aStr.upcase
        end
        
    protected
        def prot( aStr )            
            return aStr << '!!!!!!'
        end        
        
    public        
        
        def exclaim( anOb )  # calls a protected method
            puts( anOb.prot( "This is a #{anOb.class} - hurrah" ) )
        end
        
        def shout( anOb )    # calls a private method 
            puts( anOb.priv( "This is a #{anOb.class} - hurrah" ) )
        end
            
end    
class MyOtherClass < MyClass
    
end
class MyUnrelatedClass
    
end

myob = MyClass.new
myotherob = MyOtherClass.new
myunrelatedob = MyUnrelatedClass.new

private方法, 以下失败 : 
myob.shout( myotherob )
C:/Users/digoal/Desktop/new.rb:20:in `shout': private method `priv' called for #<MyOtherClass:0x1df04d8> (NoMethodError)
 from C:/Users/digoal/Desktop/new.rb:35:in `<main>'
myotherob.shout( myob )
C:/Users/digoal/Desktop/new.rb:20:in `shout': private method `priv' called for #<MyClass:0x1e204f0> (NoMethodError)
 from C:/Users/digoal/Desktop/new.rb:35:in `<main>'

protected方法, 最后一条失败 : 
myob.exclaim( myotherob )    # This is OK
myotherob.exclaim( myob )    # And so is this...
myob.exclaim( myunrelatedob )    # But this won’t work
结果 : 
C:/Users/digoal/Desktop/new.rb:16:in `exclaim': undefined method `prot' for #<MyUnrelatedClass:0x1d903a0> (NoMethodError)
 from C:/Users/digoal/Desktop/new.rb:37:in `<main>'
This is a MyOtherClass - hurrah!!!!!!
This is a MyClass - hurrah!!!!!!


10. use send method invading the privacy of private method
调用方法还可以通过send方法, 以下是api的send解释 :
class X
    private
        def priv( aStr, bStr )
             puts("I'm private, " << aStr << bStr)
        end         
end
ob = X.new
# ob.priv( "hello" )      # This fails
ob.send( :priv, "hello", " world" )    # This succeeds

执行结果 : 
I'm private, hello world

虽然这么做是可行的, 不过既然要直接调用还不如写成public方法呢.

11. Singleton Class Methods
在第5点的singleton method例子已经讲过了, 再次略过.

12. Nested Method
内嵌方法指的是在方法中嵌套方法, 一般用于一些在方法中可能重复执行的部分.
嵌套方法不能通过对象直接调用, 但是当先执行过最外层的方法后, 就可以直接调用了.
class X
   def outer_x
      print( "x:" )
      def nested_y
         print("ha! ")
      end   
      
      def nested_z
         print( "z:" )
         nested_y
      end
      
      nested_y
      nested_z
   end
end

ob1 = X.new
ob2 = X.new

# ob2.nested_z # 在它的外部方法被调用之前, 它不能被调用.
ob1.outer_x
print("\n")
ob2.nested_z  # 虽然是ob1调用的外部方法 , ob2 也可以调用这个内嵌方法了.

执行结果 : 
x:ha! z:ha! 
z:ha!

13. Method Names
方法名, 协定是小写开头的.
就像常量我们一般是大写字母开头, class也是大写字母开头.
如果把方法名写成大写字母开头会怎么样呢? 来看个例子 : 
1.9 不会有问题, 1.8会告诉你这个常量未定义 : 
如果真定义了大写开头的方法, 并且遇到了这个错误怎么办呢? 加个括号来表示我要调用这个方法.
class Myclass
  def Method1  # 还是根据协定的语义来定义比较靠谱, 小写开头.
    puts("this is a method")
  end
end
mobj1 = Myclass.new
mobj1.Method1
mobj1.Method1()  # 一般加个括号比较靠谱



【参考】
The Book Of Ruby
Ruby 1.9.3 API

目录
相关文章
|
移动开发 关系型数据库 Ruby