类与模块
在类定义中,使用一致的结构。
[link]class Person
# 首先是 extend 与 include
extend SomeModule
include AnotherModule
# 内部类
CustomError = Class.new(StandardError)
# 接着是常量
SOME_CONSTANT = 20
# 接下来是属性宏
attr_reader :name
# 跟着是其他宏(如果有的话)
validates :name
# 公开的类方法接在下一行
def self.some_method
end
# 初始化方法在类方法和实例方法之间
def initialize
end
# 跟着是公开的实例方法
def some_method
end
# 受保护及私有的方法等放在接近结尾的地方
protected
def some_protected_method
end
private
def some_private_method
end
end
在混入多个模块时,倾向使用多行语法。
[link]# 差
class Person
include Foo, Bar
end
# 好
class Person
include Foo
include Bar
end
如果嵌套类数目较多,进而导致外围类定义较长,则将它们从外围类中提取出来,分别放置在单独的以嵌套类命名的文件中,并将文件归类至以外围类命名的文件夹下。
[link]# 差
# foo.rb
class Foo
class Bar
# 定义 30 多个方法
end
class Car
# 定义 20 多个方法
end
# 定义 30 多个方法
end
# 好
# foo.rb
class Foo
# 定义 30 多个方法
end
# foo/bar.rb
class Foo
class Bar
# 定义 30 多个方法
end
end
# foo/car.rb
class Foo
class Car
# 定义 20 多个方法
end
end
定义只有类方法的数据类型时,倾向使用模块而不是类。只有当需要实例化时才使用类。
[link]# 差
class SomeClass
def self.some_method
# 省略主体
end
def self.some_other_method
# 省略主体
end
end
# 好
module SomeModule
module_function
def some_method
# 省略主体
end
def some_other_method
# 省略主体
end
end
当你想将模块的实例方法变成类方法时,倾向使用module_function
而不是extend self
。
[link]# 差
module Utilities
extend self
def parse_something(string)
# 做一些事情
end
def other_utility_method(number, string)
# 做一些事情
end
end
# 好
module Utilities
module_function
def parse_something(string)
# 做一些事情
end
def other_utility_method(number, string)
# 做一些事情
end
end
总是替那些用以表示领域模型的类提供一个适当的to_s
方法。
[link]class Person
attr_reader :first_name, :last_name
def initialize(first_name, last_name)
@first_name = first_name
@last_name = last_name
end
def to_s
"#{@first_name} #{@last_name}"
end
end
使用attr
系列方法来定义琐碎的存取器或修改器。
[link]# 差
class Person
def initialize(first_name, last_name)
@first_name = first_name
@last_name = last_name
end
def first_name
@first_name
end
def last_name
@last_name
end
end
# 好
class Person
attr_reader :first_name, :last_name
def initialize(first_name, last_name)
@first_name = first_name
@last_name = last_name
end
end
对于访问器方法,避免使用get_
作为名字前缀;对于更改器方法,避免使用set_
作为名字前缀。Ruby 语言中,通常使用attr_name
作为访问器的方法名,使用attr_name=
作为更改器的方法名。
[link]# 差
class Person
def get_name
"#{@first_name} #{@last_name}"
end
def set_name(name)
@first_name, @last_name = name.split(' ')
end
end
# 好
class Person
def name
"#{@first_name} #{@last_name}"
end
def name=(name)
@first_name, @last_name = name.split(' ')
end
end
避免使用attr
。使用attr_reader
与attr_accessor
来替代。
[link]# 差 - 创建单个存取方法(此方法在 Ruby 1.9 之后被移除了)
attr :something, true
attr :one, :two, :three # 类似于 attr_reader
# 好
attr_accessor :something
attr_reader :one, :two, :three
优先考虑使用Struct.new
。它替你定义了那些琐碎的访问器、构造器及比较操作符。
[link]# 好
class Person
attr_accessor :first_name, :last_name
def initialize(first_name, last_name)
@first_name = first_name
@last_name = last_name
end
end
# 更好
Person = Struct.new(:first_name, :last_name) do
end
不要扩展Struct.new
实例化后的对象。对它进行扩展不但引入了毫无意义的类层次,而且在此文件被多次引入时可能会产生奇怪的错误。
[link]# 差
class Person < Struct.new(:first_name, :last_name)
end
# 好
Person = Struct.new(:first_name, :last_name)
优先考虑通过工厂方法的方式创建某些具有特定意义的实例对象。
[link]class Person
def self.create(options_hash)
# 省略主体
end
end
-
# 差
class Animal
# 抽象方法
def speak
end
end
# 继承父类
class Duck < Animal
def speak
puts 'Quack! Quack'
end
end
# 继承父类
class Dog < Animal
def speak
puts 'Bau! Bau!'
end
end
# 好
class Duck
def speak
puts 'Quack! Quack'
end
end
class Dog
def speak
puts 'Bau! Bau!'
end
end
避免使用类变量(@@
)。类变量在继承方面存在令人生厌的行为。
[link]class Parent
@@class_var = 'parent'
def self.print_class_var
puts @@class_var
end
end
class Child < Parent
@@class_var = 'child'
end
Parent.print_class_var # => 此处打印的结果为 'child'
如你所见,在类的层次结构中所有类都会共享同一类变量。通常情况下,倾向使用类实例变量而不是类变量。
根据方法的目的与用途设置适当的可见级别(private
、protected
)。不要什么都不做就把所有方法设置为public
(默认值)。毕竟我们写的是 Ruby 而不是 Python。
[link]
把public
、protected
、private
与其作用的方法缩排在同一层级。且在其上下各留一行以强调此可见级别作用于之后的所有方法。
[link]class SomeClass
def public_method
# ...
end
private
def private_method
# ...
end
def another_private_method
# ...
end
end
使用def self.method
定义类方法。这种做法使得在代码重构时,即使修改了类名也无需做多次修改。
[link]class TestClass
# 差
def TestClass.some_method
# 省略主体
end
# 好
def self.some_other_method
# 省略主体
end
# 在需要定义多个类方法时,另一种便捷写法
class << self
def first_method
# 省略主体
end
def second_method_etc
# 省略主体
end
end
end
在类的词法作用域中定义方法别名时,倾向使用alias
。因为定义期间alias
与self
指向的都是词法作用域,除非明确说明,否则该别名所引用的方法不会在运行期间被改变,或是在任何子类中被修改。
[link]class Westerner
def first_name
@names.first
end
alias given_name first_name
end
因为
alias
与def
一样都是关键字,倾向使用裸字而不是符号或字符串。也就是说,使用alias foo bar
而不是alias :foo :bar
。另外需要了解 Ruby 是如何处理别名和继承的:别名所引用的原始方法是在定义期间被指定的,而不是运行期间。
class Fugitive < Westerner
def first_name
'Nobody'
end
end
在这个例子中,
Fugitive#given_name
仍然调用原先的Westerner#first_name
方法,而不是Fugitive#first_name
。如果想要覆写Fugitive#given_name
,必须在子类中重新定义。class Fugitive < Westerner
def first_name
'Nobody'
end
alias given_name first_name
end
在运行期间定义模块方法、类方法、单件方法的别名时,总是使用alias_method
。在上述情况下,使用alias
可能会导致预期之外的结果。
[link]module Mononymous
def self.included(other)
other.class_eval { alias_method :full_name, :given_name }
end
end
class Sting < Westerner
include Mononymous
end
在模块方法,或是类方法内部调用自身其他方法时,通常省略模块名/类名/self
。
[link]class TestClass
# 差
def self.call(param1, param2)
TestClass.new(param1).call(param2)
end
# 差
def self.call(param1, param2)
self.new(param1).call(param2)
end
# 好
def self.call(param1, param2)
new(param1).call(param2)
end
# 省略其他方法
end