Ruby 编程/语法/类
类 是用于创建 对象 实例的基本模板。类由表示内部状态的变量集合和提供操作该状态的行为的方法组成。
类在 Ruby 中使用 class
关键字后跟一个名称来定义。名称必须以大写字母开头,按照惯例,包含多个单词的名称将一起运行,每个单词首字母大写,没有分隔符 (驼峰式命名法)。类定义可以包含方法、类变量和实例变量声明,以及在读取时在类上下文中执行的方法调用,例如 attr_accessor。类声明以 end
关键字结束。
示例
class MyClass
def some_method
end
end
实例变量是为每个类实例创建的,并且只能在该实例内访问。它们使用 @ 运算符访问。在类定义之外,实例变量的值只能通过该实例的公共方法读取或修改。
示例
class MyClass
@one = 1
def do_something
@one = 2
end
def output
puts @one
end
end
instance = MyClass.new
instance.output
instance.do_something
instance.output
令人惊讶的是,这会输出
nil 2
发生这种情况(第一行输出为 nil)是因为在 class MyClass 以下定义的 @one 是属于类对象的实例变量(注意这与类变量不同,不能称为 @@one),而在 do_something 方法内部定义的 @one 是属于 MyClass 实例的实例变量。它们是两个不同的变量,第一个变量只能在类方法中访问。
如上一节所述,实例变量只能在实例方法定义中直接访问或修改。如果您想从外部提供对其的访问权限,则需要定义公共访问器方法,例如
class MyClass
def initialize
@foo = 28
end
def foo
return @foo
end
def foo=(value)
@foo = value
end
end
instance = MyClass.new
puts instance.foo
instance.foo = 496
puts instance.foo
请注意,ruby 提供了一些语法糖,使其看起来像您正在直接获取和设置变量;在幕后
a = instance.foo
instance.foo = b
是调用 foo 和 foo= 方法
a = instance.foo()
instance.foo=(b)
由于这是一个非常常见的用例,因此还有一个便利方法来自动生成这些 getter 和 setter
class MyClass
attr_accessor :foo
def initialize
@foo = 28
end
end
instance = MyClass.new
puts instance.foo
instance.foo = 496
puts instance.foo
与上面的程序做同样的事情。attr_accessor 方法在读取时运行,当 ruby 正在构建类对象时,它会生成 foo 和 foo= 方法。
但是,访问器方法不需要仅仅透明地访问实例变量。例如,我们可以确保所有值在存储到 foo 中之前都被四舍五入
class MyClass
def initialize
@foo = 28
end
def foo
return @foo
end
def foo=(value)
@foo = value.round
end
end
instance = MyClass.new
puts instance.foo
instance.foo = 496.2
puts instance.foo #=> 496
类变量使用 @@ 运算符访问。这些变量与类层次结构相关联,而不是与类的任何对象实例相关联,并且在所有对象实例中都是相同的。(这些类似于 Java 或 C++ 中的类“静态”变量)。
示例
class MyClass
@@value = 1
def add_one
@@value= @@value + 1
end
def value
@@value
end
end
instanceOne = MyClass.new
instanceTwo = MyClass.new
puts instanceOne.value
instanceOne.add_one
puts instanceOne.value
puts instanceTwo.value
输出
1 2 2
类可以有实例变量。这使得每个类都有一个变量,该变量不会被继承链中的其他类共享。
class Employee
class << self; attr_accessor :instances; end
def store
self.class.instances ||= []
self.class.instances << self
end
def initialize name
@name = name
end
end
class Overhead < Employee; end
class Programmer < Employee; end
Overhead.new('Martin').store
Overhead.new('Roy').store
Programmer.new('Erik').store
puts Overhead.instances.size # => 2
puts Programmer.instances.size # => 1
有关更多详细信息,请参见 MF Bliki:类实例变量
类方法的声明方式与普通方法相同,只是它们以 self
或类名后跟一个句点为前缀。这些方法在类级别执行,可以在没有对象实例的情况下调用。它们无法访问实例变量,但可以访问类变量。
示例
class MyClass
def self.some_method
puts 'something'
end
end
MyClass.some_method
输出
something
对象实例是通过称为 实例化 的过程从类创建的。在 Ruby 中,这通过类方法 new
进行。
示例
anObject = MyClass.new(parameters)
此函数在内存中设置对象,然后如果存在,将控制权委托给类的 initialize 函数。传递给 new 函数的参数将传递给 initialize
函数。
class MyClass
def initialize(parameters)
end
end
默认情况下,Ruby 类中的所有方法都是公有的 - 任何人都可以访问。但是,对于此规则只有两个例外:在 Object 类下定义的全局方法,以及任何类的 initialize 方法。它们都是隐式私有的。
如果需要,可以通过 public、private、protected 对象方法限制方法的访问权限。
有趣的是,这些实际上不是关键字,而是真正操作类的方法,动态地改变方法的可见性,因此,这些“关键字”会影响所有后续声明的可见性,直到设置新的可见性或声明体结束为止。
简单示例
class Example
def methodA
end
private # all following methods in this class are private, so are inaccessible to outside objects
def methodP
end
end
如果 private 在没有参数的情况下调用,它将为所有后续方法设置私有访问权限。它也可以使用命名参数调用。
命名私有方法示例
class Example
def methodA
end
def methodP
end
private :methodP
end
这里 private
使用一个参数调用,并将 methodP
的可见性设置为私有。
请注意,类方法(使用 def ClassName.method_name
声明)必须使用 private_class_method
函数设置为私有。
private_class_method
的一个常见用法是使构造函数方法 new
不可访问,从而迫使通过某些 getter 函数访问对象。典型的 Singleton 实现就是一个明显的例子
class SingletonLike
private_class_method :new
def SingletonLike.create(*args, &block)
@@inst = new(*args, &block) unless @@inst
return @@inst
end
end
以下是另一种常见的编写相同声明的方式
class SingletonLike
private_class_method :new
def SingletonLike.create(*args, &block)
@@inst ||= new(*args, &block)
end
end
虽然在 C++ 中 private
表示“对该类私有”,但在 Ruby 中它表示“对该实例私有”。这意味着 C++ 允许在给定类中,任何代码都可以访问该类中任何对象的私有方法。另一方面,在 Ruby 中,私有方法是它们所属的实例化的对象本地的。
private
方法不能使用显式接收器调用这一事实由以下代码说明。
class AccessPrivate
def a
end
private :a # a is private method
def accessing_private
a # sure!
self.a # nope! private methods cannot be called with an explicit receiver, even if that receiver is "self"
other_object.a # nope, a is private, so you can't get it (but if it was protected, you could!)
end
end
这里,other_object
是方法 a
所调用的“接收者”。对于私有方法,它不起作用。但是,“保护”可见性将允许这样做。
方法的默认可见性级别为“公共”,可以使用 public
方法手动指定此可见性级别。
我不确定为什么指定了它——可能是为了完整性,可能是为了让你能够在某个时刻动态地将某个方法设为私有,然后在稍后时间再设为公共。
在 Ruby 中,可见性是完全动态的。你可以在运行时更改方法的可见性!
现在,“保护”值得更多讨论。那些来自 Java 或 C++ 的人已经了解到,在这些语言中,如果一个方法是“私有”,它的可见性将被限制在声明它的类中,如果方法是“保护”,它将对该类的子类(从父类继承的类)或该包中的其他类可见。
在 Ruby 中,“私有”可见性类似于 Java 中的“保护”。Ruby 中的私有方法可以从子类访问。你无法在 Ruby 中拥有真正的私有方法;你无法完全隐藏一个方法。
保护和私有之间的区别很微妙。如果一个方法是保护的,它可以被定义该类的任何实例或其子类调用。如果一个方法是私有的,它只能在调用对象的上下文中调用——永远不可能直接访问另一个对象实例的私有方法,即使该对象与调用者是同一个类。对于保护方法,它们可以从同一个类(或子类)的对象访问。
因此,从对象“a1”(类 A 的实例)内部,你只能调用实例“a1”(self)的私有方法。你不能调用对象“a2”(也是类 A 的实例)的私有方法——它们对 a2 来说是私有的。但你可以调用对象“a2”的保护方法,因为对象 a1 和 a2 都是类 A 的实例。
Ruby FAQ 给出了以下示例——实现一个运算符,将一个内部变量与同一个类中的一个变量进行比较(用于比较对象的目的)
def <=>(other)
self.age <=> other.age
end
如果 age 是私有的,此方法将无法工作,因为 other.age 无法访问。如果“age”是保护的,这将正常工作,因为 self 和 other 是同一个类,可以访问彼此的保护方法。
要考虑这一点,保护实际上让我想起了 C# 中的“内部”访问修饰符或 Java 中的“默认”访问修饰符(当没有访问关键字设置在方法或变量上时):方法的访问方式与“公共”相同,但仅适用于同一个包内的类。
请注意,对象实例变量不是真正的私有的,你只是看不见它们。要访问实例变量,你需要创建一个 getter 和 setter。
像这样(不,不要手动这样做!请参见下方)
class GotAccessor
def initialize(size)
@size = size
end
def size
@size
end
def size=(val)
@size = val
end
end
# you could access the @size variable as
# a = GotAccessor.new(5)
# x = a.size
# a.size = y
幸运的是,我们有专门的函数来做这件事:attr_accessor、attr_reader、attr_writer。attr_accessor 会给你 get/set 功能,reader 仅提供 getter,writer 仅提供 setter。
现在缩减到
class GotAccessor
def initialize(size)
@size = size
end
attr_accessor :size
end
# attr_accessor generates variable @size accessor methods automatically:
# a = GotAccessor.new(5)
# x = a.size
# a.size = y
一个类可以从超类继承功能和变量,有时被称为父类或基类。Ruby 不支持多重继承,因此 Ruby 中的类只能有一个超类。语法如下
class ParentClass
def a_method
puts 'b'
end
end
class SomeClass < ParentClass # < means inherit (or "extends" if you are from Java background)
def another_method
puts 'a'
end
end
instance = SomeClass.new
instance.another_method
instance.a_method
输出
a b
所有非私有变量和方法都从超类继承到子类。
如果你的类覆盖了父类(超类)的方法,你仍然可以使用 'super' 关键字访问父类的方法。
class ParentClass
def a_method
puts 'b'
end
end
class SomeClass < ParentClass
def a_method
super
puts 'a'
end
end
instance = SomeClass.new
instance.a_method
输出
b a
(因为 a_method 也调用了父类的方法)。
如果你有一条很深的继承链,并且仍然想直接访问某个父类(超类)的方法,你做不到。super 只能为你提供直接父类的方法。但存在一种解决方法!当从一个类继承时,你可以将父类方法别名为另一个名称。然后,你可以通过别名访问方法。
class X
def foo
"hello"
end
end
class Y < X
alias xFoo foo
def foo
xFoo + "y"
end
end
class Z < Y
def foo
xFoo + "z"
end
end
puts X.new.foo
puts Y.new.foo
puts Z.new.foo
输出
hello helloy helloz
首先,你需要了解模块 Ruby 模块。模块是一种将一些函数、变量和类分组在一起的方式,有点像类,但更像是命名空间。因此,模块并不是真正的类。你不能实例化模块,并且模块不能使用 Self 来引用自身。模块可以拥有模块方法(就像类可以拥有类方法)以及实例方法。
不过,我们可以将模块包含到类中。将它混合进来,可以这么说。
module A
def a1
puts 'a1 is called'
end
end
module B
def b1
puts 'b1 is called'
end
end
module C
def c1
puts 'c1 is called'
end
end
class Test
include A
include B
include C
def display
puts 'Modules are included'
end
end
object=Test.new
object.display
object.a1
object.b1
object.c1
输出
Modules are included
a1 is called
b1 is called
c1 is called
模块可以通过几种不同的方式混合到类中。在深入研究本节内容之前,了解对象如何将消息解析为方法名称非常重要。
module A
def a1
puts "a1 from module"
end
end
class Test
include A
def a1
puts "a1 from class"
end
end
test = Test.new
test.a1
输出
a1 from class
使用include 时,类提供的方法会在模块提供的方法之前被搜索。这意味着来自类的函数将首先被找到并执行。
module A
def a1
puts "a1 from module"
end
end
class Test
prepend A
def a1
puts "a1 from class"
end
end
test = Test.new
test.a1
输出
a1 from module
使用prepend 时,模块提供的方法会在类提供的方法之前被搜索。这意味着来自插入的模块的函数将首先被找到并执行。
代码展示了使用模块的多重继承。
为了符合 Ruby 的一切皆对象的原则,类本身是类 Class 的实例。它们存储在声明它们的模块的作用域下的常量中。对对象实例上的方法的调用被委托给对象内部的一个变量,该变量包含对该对象类的引用。方法实现存在于类实例对象本身。类方法在元类上实现,这些元类以与这些类实例链接到它们相同的方式链接到现有的类实例对象。这些元类对大多数 Ruby 函数隐藏。