类与模块


  • 在类定义中,使用一致的结构。
    [link]

    1. class Person
    2. # 首先是 extend 与 include
    3. extend SomeModule
    4. include AnotherModule
    5. # 内部类
    6. CustomError = Class.new(StandardError)
    7. # 接着是常量
    8. SOME_CONSTANT = 20
    9. # 接下来是属性宏
    10. attr_reader :name
    11. # 跟着是其他宏(如果有的话)
    12. validates :name
    13. # 公开的类方法接在下一行
    14. def self.some_method
    15. end
    16. # 初始化方法在类方法和实例方法之间
    17. def initialize
    18. end
    19. # 跟着是公开的实例方法
    20. def some_method
    21. end
    22. # 受保护及私有的方法等放在接近结尾的地方
    23. protected
    24. def some_protected_method
    25. end
    26. private
    27. def some_private_method
    28. end
    29. end

  • 在混入多个模块时,倾向使用多行语法。
    [link]

    1. # 差
    2. class Person
    3. include Foo, Bar
    4. end
    5. # 好
    6. class Person
    7. include Foo
    8. include Bar
    9. end

  • 如果嵌套类数目较多,进而导致外围类定义较长,则将它们从外围类中提取出来,分别放置在单独的以嵌套类命名的文件中,并将文件归类至以外围类命名的文件夹下。
    [link]

    1. # 差
    2. # foo.rb
    3. class Foo
    4. class Bar
    5. # 定义 30 多个方法
    6. end
    7. class Car
    8. # 定义 20 多个方法
    9. end
    10. # 定义 30 多个方法
    11. end
    12. # 好
    13. # foo.rb
    14. class Foo
    15. # 定义 30 多个方法
    16. end
    17. # foo/bar.rb
    18. class Foo
    19. class Bar
    20. # 定义 30 多个方法
    21. end
    22. end
    23. # foo/car.rb
    24. class Foo
    25. class Car
    26. # 定义 20 多个方法
    27. end
    28. end

  • 定义只有类方法的数据类型时,倾向使用模块而不是类。只有当需要实例化时才使用类。
    [link]

    1. # 差
    2. class SomeClass
    3. def self.some_method
    4. # 省略主体
    5. end
    6. def self.some_other_method
    7. # 省略主体
    8. end
    9. end
    10. # 好
    11. module SomeModule
    12. module_function
    13. def some_method
    14. # 省略主体
    15. end
    16. def some_other_method
    17. # 省略主体
    18. end
    19. end

  • 当你想将模块的实例方法变成类方法时,倾向使用 module_function 而不是 extend self
    [link]

    1. # 差
    2. module Utilities
    3. extend self
    4. def parse_something(string)
    5. # 做一些事情
    6. end
    7. def other_utility_method(number, string)
    8. # 做一些事情
    9. end
    10. end
    11. # 好
    12. module Utilities
    13. module_function
    14. def parse_something(string)
    15. # 做一些事情
    16. end
    17. def other_utility_method(number, string)
    18. # 做一些事情
    19. end
    20. end

  • 当设计类的层次结构时,确保它们符合里式替换原则
    [link]


  • 让你的类尽量满足 SOLID 原则) 。
    [link]


  • 总是替那些用以表示领域模型的类提供一个适当的 to_s 方法。
    [link]

    1. class Person
    2. attr_reader :first_name, :last_name
    3. def initialize(first_name, last_name)
    4. @first_name = first_name
    5. @last_name = last_name
    6. end
    7. def to_s
    8. "#{@first_name} #{@last_name}"
    9. end
    10. end

  • 使用 attr 系列方法来定义琐碎的存取器或修改器。
    [link]

    1. # 差
    2. class Person
    3. def initialize(first_name, last_name)
    4. @first_name = first_name
    5. @last_name = last_name
    6. end
    7. def first_name
    8. @first_name
    9. end
    10. def last_name
    11. @last_name
    12. end
    13. end
    14. # 好
    15. class Person
    16. attr_reader :first_name, :last_name
    17. def initialize(first_name, last_name)
    18. @first_name = first_name
    19. @last_name = last_name
    20. end
    21. end

  • 对于访问器方法,避免使用 get_ 作为名字前缀;对于更改器方法,避免使用 set_ 作为名字前缀。Ruby 语言中,通常使用 attr_name 作为访问器的方法名,使用 attr_name= 作为更改器的方法名。
    [link]

    1. # 差
    2. class Person
    3. def get_name
    4. "#{@first_name} #{@last_name}"
    5. end
    6. def set_name(name)
    7. @first_name, @last_name = name.split(' ')
    8. end
    9. end
    10. # 好
    11. class Person
    12. def name
    13. "#{@first_name} #{@last_name}"
    14. end
    15. def name=(name)
    16. @first_name, @last_name = name.split(' ')
    17. end
    18. end

  • 避免使用 attr。使用 attr_readerattr_accessor 来替代。
    [link]

    1. # 差 - 创建单个存取方法(此方法在 Ruby 1.9 之后被移除了)
    2. attr :something, true
    3. attr :one, :two, :three # 类似于 attr_reader
    4. # 好
    5. attr_accessor :something
    6. attr_reader :one, :two, :three

  • 优先考虑使用 Struct.new。它替你定义了那些琐碎的访问器、构造器及比较操作符。
    [link]

    1. # 好
    2. class Person
    3. attr_accessor :first_name, :last_name
    4. def initialize(first_name, last_name)
    5. @first_name = first_name
    6. @last_name = last_name
    7. end
    8. end
    9. # 更好
    10. Person = Struct.new(:first_name, :last_name) do
    11. end

  • 不要扩展 Struct.new 实例化后的对象。对它进行扩展不但引入了毫无意义的类层次,而且在此文件被多次引入时可能会产生奇怪的错误。
    [link]

    1. # 差
    2. class Person < Struct.new(:first_name, :last_name)
    3. end
    4. # 好
    5. Person = Struct.new(:first_name, :last_name)

  • 优先考虑通过工厂方法的方式创建某些具有特定意义的实例对象。
    [link]

    1. class Person
    2. def self.create(options_hash)
    3. # 省略主体
    4. end
    5. end

  • 倾向使用鸭子类型而不是继承。
    [link]

    1. # 差
    2. class Animal
    3. # 抽象方法
    4. def speak
    5. end
    6. end
    7. # 继承父类
    8. class Duck < Animal
    9. def speak
    10. puts 'Quack! Quack'
    11. end
    12. end
    13. # 继承父类
    14. class Dog < Animal
    15. def speak
    16. puts 'Bau! Bau!'
    17. end
    18. end
    19. # 好
    20. class Duck
    21. def speak
    22. puts 'Quack! Quack'
    23. end
    24. end
    25. class Dog
    26. def speak
    27. puts 'Bau! Bau!'
    28. end
    29. end

  • 避免使用类变量(@@)。类变量在继承方面存在令人生厌的行为。
    [link]

    1. class Parent
    2. @@class_var = 'parent'
    3. def self.print_class_var
    4. puts @@class_var
    5. end
    6. end
    7. class Child < Parent
    8. @@class_var = 'child'
    9. end
    10. Parent.print_class_var # => 此处打印的结果为 'child'

    如你所见,在类的层次结构中所有类都会共享同一类变量。通常情况下,倾向使用类实例变量而不是类变量。


  • 根据方法的目的与用途设置适当的可见级别(privateprotected)。不要什么都不做就把所有方法设置为 public(默认值)。毕竟我们写的是 Ruby 而不是 Python
    [link]


  • publicprotectedprivate 与其作用的方法缩排在同一层级。且在其上下各留一行以强调此可见级别作用于之后的所有方法。
    [link]

    1. class SomeClass
    2. def public_method
    3. # ...
    4. end
    5. private
    6. def private_method
    7. # ...
    8. end
    9. def another_private_method
    10. # ...
    11. end
    12. end

  • 使用 def self.method 定义类方法。这种做法使得在代码重构时,即使修改了类名也无需做多次修改。
    [link]

    1. class TestClass
    2. # 差
    3. def TestClass.some_method
    4. # 省略主体
    5. end
    6. # 好
    7. def self.some_other_method
    8. # 省略主体
    9. end
    10. # 在需要定义多个类方法时,另一种便捷写法
    11. class << self
    12. def first_method
    13. # 省略主体
    14. end
    15. def second_method_etc
    16. # 省略主体
    17. end
    18. end
    19. end

  • 在类的词法作用域中定义方法别名时,倾向使用 alias。因为定义期间 aliasself 指向的都是词法作用域,除非明确说明,否则该别名所引用的方法不会在运行期间被改变,或是在任何子类中被修改。
    [link]

    1. class Westerner
    2. def first_name
    3. @names.first
    4. end
    5. alias given_name first_name
    6. end

    因为 aliasdef 一样都是关键字,倾向使用裸字而不是符号或字符串。也就是说,使用 alias foo bar 而不是 alias :foo :bar

    另外需要了解 Ruby 是如何处理别名和继承的:别名所引用的原始方法是在定义期间被指定的,而不是运行期间。

    1. class Fugitive < Westerner
    2. def first_name
    3. 'Nobody'
    4. end
    5. end

    在这个例子中,Fugitive#given_name 仍然调用原先的 Westerner#first_name 方法,而不是 Fugitive#first_name。如果想要覆写 Fugitive#given_name,必须在子类中重新定义。

    1. class Fugitive < Westerner
    2. def first_name
    3. 'Nobody'
    4. end
    5. alias given_name first_name
    6. end

  • 在运行期间定义模块方法、类方法、单件方法的别名时,总是使用 alias_method。在上述情况下,使用 alias 可能会导致预期之外的结果。
    [link]

    1. module Mononymous
    2. def self.included(other)
    3. other.class_eval { alias_method :full_name, :given_name }
    4. end
    5. end
    6. class Sting < Westerner
    7. include Mononymous
    8. end

  • 在模块方法,或是类方法内部调用自身其他方法时,通常省略模块名/类名/self
    [link]

    1. class TestClass
    2. # 差
    3. def self.call(param1, param2)
    4. TestClass.new(param1).call(param2)
    5. end
    6. # 差
    7. def self.call(param1, param2)
    8. self.new(param1).call(param2)
    9. end
    10. # 好
    11. def self.call(param1, param2)
    12. new(param1).call(param2)
    13. end
    14. # 省略其他方法
    15. end