宏和类级访问器方法的最佳实践和实现?

时间:2011-03-22 11:35:03

标签: ruby macros metaprogramming class-design accessor

我正在设计/构建一个类系统,这些类都派生自一个基类。

目标是使用易于使用的继承宏方法,如下所示:

class Something < A::Base
  full_name 'Something that goes bump in the night.'
end

任何代码都应该能够通过类级访问器方法向类询问此信息(或者,可能是规范化/派生信息)。

puts Something.full_name
  # => "Some kind of calculated value that may or may not go bump in the night."

鉴于A::Base包含/ extends / somehow-otherwise-mixes-in两个模块的宏方法都是这样的:

module MacroMethods
  private
  def full_name(full_name)
    # non-trivial, one-time-only set-up code exists here in actual usage
  end
end

以及具有类级别访问器方法的模块,其工作方式如下:

module AccessorMethods
  public
  def full_name
    # a non-trivial, runtime-calculated value is returned here in actual usage
  end
end

无论我如何将它们混合在一起,我都会在两者之间不断遇到命名冲突(即'错误的参数数量(1为0)(ArgumentError)')。

注意: full_name是所需内容的最简单示例;其他更复杂的宏/访问器确保宏方法的非灵活约束需要在类中声明,并且需要一次性设置一次。


我的问题是双重的:

  1. 有没有办法让这一切都在A::Base课程内完成?
  2. 这是在Ruby中执行此操作的正确方法吗?有更好的方法可以实现相同的结果吗?
  3. 已考虑的选项:

    • 调用宏或访问者方法。
      (例如,在Something类中:set_up_full_name 'Something that …'
      缺点是命名令人困惑且非常规。

    • 使访问者方法实例级而不是类级别。
      (例如puts a_something.full_name'
      缺点是宏设置的特征是类固有的特征,而不是每个实例(在某些情况下,只有对类的引用可用,而不是实例)

    • 创建一个处理宏和访问者功能的方法。
      (例如在A ::基类:def self.full_name(*args) …
      缺点是宏方法不再是私有的,RDoc看起来像sh * t。

    • 改为使用abstact / virtual-ish方法。
      (例如,在Something类中:def self.full_name; 'Something that …'; end
      缺点是这是子类中的更多代码,而不是一个优秀的Ruby范例,更像是Objective-C (或C ++,或Java,...)

4 个答案:

答案 0 :(得分:1)

Slipp,我仔细阅读了你的问题。您无法同时在同一对象上定义两个名为full_name的不同方法。但是,你可以这样做:

module MacroMethods
  private
  def full_name(full_name)
    # non-trivial, one-time-only set-up code exists here in actual usage
    # this method can only be called once in the definition of any given class,
    #   because after the first call, it will be redefined!
    extend AccessorMethods
  end
end

module AccessorMethods
  public
  def full_name
    # a non-trivial, runtime-calculated value is returned here in actual usage
  end
end

class Base
  extend MacroMethods
end

class Something < Base
  full_name 'after this call, I will get a new full_name method!'
end

class SomethingElse < Base
  full_name 'so will I!'
end

答案 1 :(得分:0)

如果您希望某些类可以使用类宏,那么公共基类不是Ruby解决方案。相反,您创建了一个模块,该模块使用您希望它们具有的功能扩展基类:

module Extensions
  def self.included(base_class)
    base_class.extend ClassMethods
  end

  module ClassMethods
    attr_accessor :full_name
  end
end

class Something
  include Extensions

  self.full_name = "Something that goes bump in the night"
end

puts Something.full_name    # => Something that goes bump in the night
thing = Something.new
puts thing.full_name        # Error

这会覆盖名为Extensions的{​​{1}}中的钩子方法,该方法将包含该模块的任何类作为参数传递。然后,新方法在基类上调用Module#included,将Object#extend 中可用的方法作为类方法直接放到该类上。这与定义类方法的工作方式相同,但这是动态运行的。这使您无需在提供宏的类上使用您唯一的基类。请注意,这些方法未在包含该模块的类的实例上定义。

答案 2 :(得分:0)

看起来大多数其他答案都有正确的想法,但缺少#full_name的getter方法。这个例子可能就是你要找的东西:

class Thing
  class << self
    attr_writer :full_name

    def full_name
      "My full name is #{@full_name}"
    end
  end
end

有了这个,你可以这样做:

> Thing.full_name = 'Thing Class'
=> "Thing Class"
> Thing.full_name
=> "My full name is Thing Class"

答案 3 :(得分:-1)

这似乎是不必要的复杂。为什么不在父类上使用属性?

class Base
  class << self
    attr_accessor :full_name
  end
end

class A < Base; end

class B < Base; end

A.full_name = "The full name of A"
B.full_name = "The full name of B"

puts A.full_name # "The full name of A"
puts B.full_name # "The full name of B"