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: ClassInstanceVariables
类方法的声明方式与普通方法相同,只是它们前面有 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 方法。它们都是隐式私有的。
如果需要,可以通过公有、私有、受保护的对象方法来限制方法的访问权限。
有趣的是,这些实际上不是关键字,而是对类进行操作的实际方法,它们动态地更改方法的可见性,因此,这些“关键字”会影响所有后续声明的可见性,直到设置新的可见性或声明主体结束。
简单示例
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 中,“private” 可见性类似于 Java 中的“protected”。Ruby 中的私有方法可以在子类中访问。在 Ruby 中,您无法拥有真正私有的方法;您无法完全隐藏方法。
受保护和私有的区别很细微。如果一个方法是受保护的,它可以被定义类或其子类的任何实例调用。如果一个方法是私有的,它只能在调用对象的上下文中被调用——从不可以通过直接访问另一个对象实例的私有方法,即使该对象与调用者是同一个类。对于受保护的方法,它们可以从同一个类(或子类)的对象访问。
因此,从一个对象“a1”(A 类的一个实例)内部,您只能调用“a1”(self)实例的私有方法。并且您不能调用对象“a2”(它也是 A 类)的私有方法——它们对 a2 是私有的。但您可以调用对象“a2”的受保护方法,因为对象 a1 和 a2 都是 A 类。
Ruby 常见问题解答 给出了以下示例——实现一个运算符来比较一个内部变量与同一个类中的变量(用于比较对象的目的)
def <=>(other)
self.age <=> other.age
end
如果 age 是私有的,此方法将不起作用,因为 other.age 不可访问。如果“age”是受保护的,这将正常工作,因为 self 和 other 是同一个类,并且可以访问彼此的受保护方法。
要理解这一点,受保护实际上让我想起了 C# 中的“internal”访问修饰符或 Java 中的“default”访问修饰符(当没有在方法或变量上设置访问关键字时):方法可以像“public”一样访问,但仅限于同一个包内的类。
实例变量
[edit | edit source]请注意,对象实例变量并不是真正私有的,只是您看不见它们。要访问实例变量,您需要创建一个 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
继承
[edit | edit source]一个类可以从一个超类继承功能和变量,有时被称为父类或基类。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
混合模块
[edit | edit source]首先,您需要阅读关于模块的知识 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
模块可以通过几种不同的方式混合到类中。在深入研究本节内容之前,了解对象如何将消息解析为方法名称非常重要。
包含
[edit | edit source]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时,类提供的 method 会在模块提供的 method 之前进行搜索。这意味着来自类的 method 会先被找到并执行。
预先插入
[edit | edit source]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时,模块提供的 method 会在类提供的 method 之前进行搜索。这意味着来自预先插入模块的 method 会先被找到并执行。
代码展示了使用模块进行多重继承。
Ruby 类元模型
[edit | edit source]为了符合 Ruby 的一切皆对象的原则,类本身是 Class 类的实例。它们存储在声明它们的模块作用域下的常量中。对对象实例上方法的调用被委托给对象内部的一个变量,该变量包含对该对象类的引用。方法实现存在于 Class 实例对象本身。类方法是在元类上实现的,元类以与这些类实例链接到它们相同的方式链接到现有的类实例对象。这些元类对大多数 Ruby 函数是隐藏的。