跳转到内容

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]
super 关键字只访问直接父类的方法。不过有一个解决方法。

一个类可以从一个超类继承功能和变量,有时被称为父类基类。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 函数是隐藏的。

华夏公益教科书