类定义
在类或模块定义时,其自身充当了当前对象self的角色,类和模块也都是对象,与方法和块相同,类定义也会返回最后一条语句的值.
class MyClass
puts "Hello"
end
=> Hello
当前类
尽管self可以获得当前对象,但并不能获得当前类,每当通过class关键字打开一个类时,这个类就成为当前类.
class MyClass
# 现在当前类是MyClass
def my_method
# my_method是MyClass的一个实例方法
end
end
class关键字必须指定类名才可以打开类,对于未知类名就可以修改当前类,可以使用class_eval().
class_eval()
Module#class_eval()方法会在一个已存在类的上下文中执行一个块.
def add_method_to(a_class)
a_class.class_eval do
def my_method do
"Hello!"
end
end
end
add_method_to String
"str".my_method # => "Hello!"
class_eval()方法与instance_eval()方法并不相同, instance_eval()方法仅会修改self(特定情况下也会修改当前类),而\calss_eval()方法会同时修改self和当前类.
对于二者的选择,若只想打开一个对象,并不在乎是不是类,使用instance_eval更好;而若是希望打开类,则使用class_eval()更好.
类实例变量
由于类是Class的实例,类名是常量,故而当self由当前类充当时所定义的变量即为类实例变量.
class MyClass
@my_var = 1
def self.read; @my_var; end # self为当前类MyClass
def write; @my_var = 2; end # self为调用该方法的接收者
def read; @my_var; end
end
obj = MyClass.new
obj.write
obj.read # => 2
MyClass.read # => 1
类变量
类变量与实例变量不同, 它们可以被子类或类的实例所引用, 类似java静态成员.
@@v = 1
class MyClass
@@v = 2
end
@@v # => 2
这种情况不就破坏了class域作用门的性质, 得到这种结果是因为类变量并不真正属于类,它们属于类体系结构, 由于@@v定义与man顶级作用域中,它属于main的类Object,所以也属于Object所有的后代,而MyClass继承自Object,因此也共享了这个类变量.
单件方法
Ruby允许给单个对象增加一个方法, 该方法只对这个对象有效,称为单件方法.
str1 = "abc"
str2 = "abc"
def str1.single_method
"singleton_method!"
end
str1.class # => String
str1.single_method # => "singleton_method!"
str2.class # => String
str2.single_method # => Error:NoMethodError
从上述代码可以看出同样是String类对象的str1和str2, str1有single_method方法,而str2没有, 还记得前面讲过,对象保存的只是一组实例变量和它自身类的引用,对象的方法都保存在自身类中,很明显single_method方法并不在str2的祖先链中, 那么single_method方法去哪了?下面的Eigenclass则告诉了我们答案.
Eigenclass
在obj上定义一个单件方法, 它存放在哪, 同样类是Class的对象, 而类方法不能被类的实例对象调用,说明类方法不在Class中,那么又存放在哪里呢?
当一个对象索要它的类时,Ruby并没有告诉我们全部,我们得到的类并不是看到的类,而是一个对象特有的隐藏类,我们称之为eigenclass.
obj = Object.new
eigenclass = class << obj
self # => Class
end
def obj.my_singleton_method; end
eigenclass.instance_methods.grep(/my_/) # => ["my_singleton_method"]
在查找方法时,会先从接收者的eigenclass中查找,若未找到,则在eigenclass的超类中查找.
一个对象的eigenclass的超类是这个对象的类;一个类的eigenclass的超类是这个类的超类的eigenclass.
module MyModule
def self.my_method; 'hello'; end
end
class MyClass
include MyModule
end
MyClass.my_method # => Error: NoMethodError
当类包含模块时,它获得的是该模块的实例方法而不是类方法,类方法存在与模块的eigenclass中. 那么如何通过包含模块来定义类方法.
module MyModule
def my_method; 'hello'; end # 首先在模块中定义普通实例方法
end
class MyClass
class << slef # 通过打开MyClass的eigenclass来包含模块
include MyModule
end
end
MyClass.my_method # => 'hello'
my_method()方法是MyClass的eigenclass的一个实例方法,这样my_method()也是MyClass的一个类方法,这种技术称为类扩展.同样该技术运用在对象中则是对象扩展.
类宏
在Java中要想访问一个类的私有属性需要get和set方法,虽然Ruby对象并没有属性,但如果想像get和set一样访问,就会定义两个拟态方法.
class MyClass
def set=(value)
@attr = value
end
def get
@attr
end
end
这样的写法会给人很枯燥的感觉,我们可以通过Module#attr_*()方法家族中的成员来定义访问器.Module#attr_reader()可以生成一个读方法, Module#attr_writer()可以生成一个写方法,Module#attr_accessor()可以同时生成两者.
所有的attr_*()方法都定义在Module中,所以不过self是类还是模块,都可以使用它们.类似与attr_accessor()的方法我们称之为类宏, 虽然类宏看起来像关键字,但它们只是普通方法,只是可以用在类定义中.
方法别名
通过使用alias关键字,可以给Ruby方法取一个别名.
class MyClass
def my_method
"my_method()"
end
alias :m :my_method # 第一个是新名字
end
obj = MyClass.new
obj.my_method # => "my_method()"
obj.m # => "my_method()"
环绕别名
class String
alias :real_length :length
def length
real_length > 5 ? "long" : "short"
edn
end
"This a test example".length # => "long"
"This a test example".real_length # => 20
上述代码虽然重写了length方法,但是别名方法还是引用的原始方法.
当重定义一个方法时,并没有真正修改这个方法,而是把当前存在的这个方法名与新定义的方法绑定起来, 只要旧方法还存在绑定的名字,仍然可以调用.旧的方法被新的方法所环绕,这种技巧称为环绕别名,步骤如下:.
1.给方法定义一个别名
2.重定义这个方法
3.在新的方法中调用老的方法