Ruby 是否可以在初始化期间包含模块?

Ruby 是否可以在初始化期间包含模块?,ruby,metaprogramming,Ruby,Metaprogramming,假设有以下模式: Foobar类,它使用Foo或Bar中的逻辑,但不能同时使用两者,具体取决于构造函数给出的参数 Foo模块,包含一些逻辑 Bar模块,包含一些备用逻辑 一个很好的例子是轨道元素计算器,它可以计算半径和速度向量,然后计算轨道元素——或者,可以计算轨道元素,然后计算半径和速度向量 为什么不以更直接的方式来做呢?我想利用实例变量进行实时计算,例如 def derived_value @derived_value ||= begin # compute only whe

假设有以下模式:

  • Foobar类,它使用Foo或Bar中的逻辑,但不能同时使用两者,具体取决于构造函数给出的参数
  • Foo模块,包含一些逻辑
  • Bar模块,包含一些备用逻辑
一个很好的例子是轨道元素计算器,它可以计算半径和速度向量,然后计算轨道元素——或者,可以计算轨道元素,然后计算半径和速度向量

为什么不以更直接的方式来做呢?我想利用实例变量进行实时计算,例如

def derived_value
  @derived_value ||= begin
    # compute only when/if necessary
  end
end
我大致尝试了以下方法:

class Orbit
  def initialize options = {}
    if [:radius,:velocity].all? { |key| options.has_key?(key) }
      # Provide instance methods which derive orbital elements from radius, velocity
      self.include Orbit::FromRadiusAndVelocity
    else
      # Provide instance methods which derive radius and velocity from orbital elements
      self.include Orbit::FromOrbitalElements
    end
    initialize_helper options
  end
end

module Orbit::FromOrbitalElements
  module ClassMethods
    class << self
      def initialize_helper options
        @argument_of_perigee = options[:argument_of_perigee]
        puts "From orbital elements"
      end

      attr_reader :argument_of_perigee
    end
  end

  def self.included(base)
    base.extend Orbit::FromOrbitalElements::ClassMethods
  end

  def self.radius
    # Instance method to calculate the radius from the orbital elements
    @radius ||= begin
      # If radius is not already defined, calculate it from some intermediate
      # values, which themselves depend upon orbital elements.
    end
  end
end

module Orbit::FromRadiusAndVelocity
  module ClassMethods
    class << self
      def initialize_helper options
        @radius = options[:radius]
        @velocity = options[:velocity]
        puts "From radius and velocity"
      end

      attr_reader :radius, :velocity
    end
  end

  def self.included(base)
    base.extend Orbit::FromRadiusAndVelocity::ClassMethods
  end

  def self.argument_of_perigee
    # Instance method to calculate an orbital element
    # (There would be more instance methods like this)
    @argument_of_perigee ||= begin
      # If argument_of_perigee is not already defined, calculate it from some intermediate
      # values, which themselves depend upon radius and velocity.
    end
  end
end
类轨道
def初始化选项={}
如果[:半径,:速度]。全部?{| key | options.has|key?(key)}
#提供从半径、速度导出轨道元素的实例方法
self.include轨道::from半径和速度
其他的
#提供从轨道元素导出半径和速度的实例方法
self.include轨道::FromOrbitalElements
结束
初始化辅助程序选项
结束
结束
模块轨道::来自轨道元素
模块类方法

类对我来说,这听起来像是一个简单继承的工作:

class FooBar
  def self.new(options)
    if [:radius,:velocity].all? { |key| options.has_key?(key) }
      Foo.new options
    else
      Bar.new options
    end
  end

  # common Foo and Barlogic here
end

class Foo < FooBar
end

class Bar < FooBar
end
class FooBar
def自我更新(选项)
如果[:半径,:速度]。全部?{| key | options.has|key?(key)}
Foo.新选项
其他的
新选项
结束
结束
#这里有常见的Foo和Barlogic
结束
类Foo
对我来说,这听起来像是一项简单继承的工作:

class FooBar
  def self.new(options)
    if [:radius,:velocity].all? { |key| options.has_key?(key) }
      Foo.new options
    else
      Bar.new options
    end
  end

  # common Foo and Barlogic here
end

class Foo < FooBar
end

class Bar < FooBar
end
class FooBar
def自我更新(选项)
如果[:半径,:速度]。全部?{| key | options.has|key?(key)}
Foo.新选项
其他的
新选项
结束
结束
#这里有常见的Foo和Barlogic
结束
类Foo
我不太清楚为什么要在一个实体下屏蔽两个不同实体的实例,但如果需要基于参数的简单决策逻辑,这是可行的:

# orbit.rb
module Orbit
  module_function

  def from_options(options = {})
    klass = if options[:radius] && options[:velocity]
              FromRadiusAndVelocity
            else
              FromOrbitalElements
            end

    klass.new(options)
  end
end

# orbit/from_orbital_elements.rb
module Orbit
  class FromOrbitalElements
    attr_reader :argument_of_perigee

    def initialize(options)
      @argument_of_perigee = options[:argument_of_perigee]

      puts 'From orbital elements'
    end

    def radius
      @radius ||= begin
                    # ...
                  end
    end
  end
end

# orbit/from_radius_and_velocity.rb
module Orbit
  class FromRadiusAndVelocity
    attr_reader :radius, :velocity

    def initialize(options)
      @radius   = options[:radius]
      @velocity = options[:velocity]

      puts 'From radius and velocity'
    end

    def argument_of_perigee
      @argument_of_perigee ||= begin
                                 # ...
                               end
    end
  end
end

# Usage
orbit = Orbit.from_options(radius: 5, velocity: 6)
orbit = Orbit.from_options(argument_of_perigee: 5)

如果您想做一些相同的接口事情,可能希望将Orbit转换为一个类并在其他类中对其进行子类化,但这在您的代码中并不清楚。

我不太清楚为什么要将两个不同实体的实例屏蔽在一个实体下,但如果您想要基于参数的简单决策逻辑,这是可行的:

# orbit.rb
module Orbit
  module_function

  def from_options(options = {})
    klass = if options[:radius] && options[:velocity]
              FromRadiusAndVelocity
            else
              FromOrbitalElements
            end

    klass.new(options)
  end
end

# orbit/from_orbital_elements.rb
module Orbit
  class FromOrbitalElements
    attr_reader :argument_of_perigee

    def initialize(options)
      @argument_of_perigee = options[:argument_of_perigee]

      puts 'From orbital elements'
    end

    def radius
      @radius ||= begin
                    # ...
                  end
    end
  end
end

# orbit/from_radius_and_velocity.rb
module Orbit
  class FromRadiusAndVelocity
    attr_reader :radius, :velocity

    def initialize(options)
      @radius   = options[:radius]
      @velocity = options[:velocity]

      puts 'From radius and velocity'
    end

    def argument_of_perigee
      @argument_of_perigee ||= begin
                                 # ...
                               end
    end
  end
end

# Usage
orbit = Orbit.from_options(radius: 5, velocity: 6)
orbit = Orbit.from_options(argument_of_perigee: 5)

如果你想做一些相同的接口事情,你可能想把Orbit变成一个类,然后在其他类中对它进行子类化,但这在你的代码中并不清楚。

你可以通过在singleton类(有时称为eigenclass)中包含选择的模块,而不是对象本身。初始值设定项可以是这样的

class Orbit
  def initialize options = {}
    if [:radius,:velocity].all? { |key| options.has_key?(key) }
      self.singleton_class.include Orbit::FromRadiusAndVelocity
    else
      self.singleton_class.include Orbit::FromOrbitalElements
    end

    initialize_helper options
  end
end
module Orbit::FromOrbitalElements
  def initialize_helper options
    @argument_of_perigee = options[:argument_of_perigee]
    puts "From orbital elements"
  end

  def self.included(base)
    base.instance_eval do
      attr_reader :argument_of_perigee
    end
  end

  def radius
    @radius ||= begin
    end
  end
end

module Orbit::FromRadiusAndVelocity
  def self.included(base)
    base.instance_eval do
      attr_reader :radius, :velocity
    end
  end

  def initialize_helper options
    @radius = options[:radius]
    @velocity = options[:velocity]
    puts "From radius and velocity"
  end

  def argument_of_perigee
    @argument_of_perigee ||= begin
    end
  end
end
如果要使用此方法,则需要相应地调整模块:

  • 不需要
    ClassMethod
    模块,所有包含的方法(假定)都是从对象调用的,而不是从类调用的
  • 从方法名称中删除
    self.
    (当然,包含的
    self除外)
  • 代码的其余部分可以如下所示

    class Orbit
      def initialize options = {}
        if [:radius,:velocity].all? { |key| options.has_key?(key) }
          self.singleton_class.include Orbit::FromRadiusAndVelocity
        else
          self.singleton_class.include Orbit::FromOrbitalElements
        end
    
        initialize_helper options
      end
    end
    
    module Orbit::FromOrbitalElements
      def initialize_helper options
        @argument_of_perigee = options[:argument_of_perigee]
        puts "From orbital elements"
      end
    
      def self.included(base)
        base.instance_eval do
          attr_reader :argument_of_perigee
        end
      end
    
      def radius
        @radius ||= begin
        end
      end
    end
    
    module Orbit::FromRadiusAndVelocity
      def self.included(base)
        base.instance_eval do
          attr_reader :radius, :velocity
        end
      end
    
      def initialize_helper options
        @radius = options[:radius]
        @velocity = options[:velocity]
        puts "From radius and velocity"
      end
    
      def argument_of_perigee
        @argument_of_perigee ||= begin
        end
      end
    end
    

    这就是说,这个解决方案只是为了演示,我建议您遵循他在回答中提到的@Ollie示例,因为它更干净。

    您可以通过在singleton类(有时称为本征类)中包含选择的模块,而不是对象本身。初始值设定项可以是这样的

    class Orbit
      def initialize options = {}
        if [:radius,:velocity].all? { |key| options.has_key?(key) }
          self.singleton_class.include Orbit::FromRadiusAndVelocity
        else
          self.singleton_class.include Orbit::FromOrbitalElements
        end
    
        initialize_helper options
      end
    end
    
    module Orbit::FromOrbitalElements
      def initialize_helper options
        @argument_of_perigee = options[:argument_of_perigee]
        puts "From orbital elements"
      end
    
      def self.included(base)
        base.instance_eval do
          attr_reader :argument_of_perigee
        end
      end
    
      def radius
        @radius ||= begin
        end
      end
    end
    
    module Orbit::FromRadiusAndVelocity
      def self.included(base)
        base.instance_eval do
          attr_reader :radius, :velocity
        end
      end
    
      def initialize_helper options
        @radius = options[:radius]
        @velocity = options[:velocity]
        puts "From radius and velocity"
      end
    
      def argument_of_perigee
        @argument_of_perigee ||= begin
        end
      end
    end
    
    如果要使用此方法,则需要相应地调整模块:

  • 不需要
    ClassMethod
    模块,所有包含的方法(假定)都是从对象调用的,而不是从类调用的
  • 从方法名称中删除
    self.
    (当然,包含的
    self除外)
  • 代码的其余部分可以如下所示

    class Orbit
      def initialize options = {}
        if [:radius,:velocity].all? { |key| options.has_key?(key) }
          self.singleton_class.include Orbit::FromRadiusAndVelocity
        else
          self.singleton_class.include Orbit::FromOrbitalElements
        end
    
        initialize_helper options
      end
    end
    
    module Orbit::FromOrbitalElements
      def initialize_helper options
        @argument_of_perigee = options[:argument_of_perigee]
        puts "From orbital elements"
      end
    
      def self.included(base)
        base.instance_eval do
          attr_reader :argument_of_perigee
        end
      end
    
      def radius
        @radius ||= begin
        end
      end
    end
    
    module Orbit::FromRadiusAndVelocity
      def self.included(base)
        base.instance_eval do
          attr_reader :radius, :velocity
        end
      end
    
      def initialize_helper options
        @radius = options[:radius]
        @velocity = options[:velocity]
        puts "From radius and velocity"
      end
    
      def argument_of_perigee
        @argument_of_perigee ||= begin
        end
      end
    end
    

    这就是说,这个解决方案只是为了演示,我建议您遵循他在回答中提到的@Ollie示例,因为它更干净。

    不清楚您要做什么。您不能将模块包含到对象中,只有
    模块的实例定义了此方法。但是,您可以扩展对象,但不能使用您编写的模块。请更新你的问题,清楚地描述你想要达到的目标。现在更清楚了吗?我不太确定我没有充分解释什么,但我很乐意尝试清理那些令人困惑的部分——如果你能指出它们给我看的话。你想做什么还不清楚。你不能将模块包含到一个对象中,只有
    模块的实例定义了这个方法。但是,您可以扩展对象,但不能使用您编写的模块。请更新你的问题,清楚地描述你想要达到的目标。现在更清楚了吗?我不太确定我没有充分解释什么,但我很乐意尝试清理那些令人困惑的部分——如果你能向我指出的话。