【问题标题】:Best Way to Abstract Initializing Attributes抽象初始化属性的最佳方法
【发布时间】:2009-07-03 06:08:15
【问题描述】:

抽象这种模式的最佳方法是什么:

class MyClass
  attr_accessor :foo, :bar

  def initialize(foo, bar)
    @foo, @bar = foo, bar
  end
end

一个好的解决方案应该考虑到超类并且能够处理仍然能够有一个初始化器来做更多的事情。在不牺牲解决方案性能的情况下加分。

【问题讨论】:

  • 这真的很模糊。你想抽象它的哪一部分?你想如何抽象它?
  • 另外,您可以尝试在这里提问:refactormycode.com
  • 我想创建一个可以混合到类中的模块或覆盖 Class 本身,这样我就可以编写一行代码并让它创建访问器和初始化程序。
  • 您是否需要能够指定非 attr_accessor 参数才能初始化?您是否设想过签名中超长的属性的世代排序?
  • 我不确定...如果创建了新的初始化程序,我想避免丢失由 initialize_with 创建的初始化程序,但我知道这会在方法签名周围引入一些混淆。也许你会这样做: Foo.new(new_arg, new_arg2, :foo => 1, :bar => 2)?这似乎也可能令人困惑。想法?

标签: ruby abstraction


【解决方案1】:

该问题的解决方案已经(部分)exists,但如果您想在您的课程中使用更具声明性的方法,那么以下应该可以工作。

class Class
  def initialize_with(*attrs, &block)
    attrs.each do |attr|
      attr_accessor attr
    end
    (class << self; self; end).send :define_method, :new do |*args|
      obj = allocate
      init_args, surplus_args = args[0...attrs.size], args[attrs.size..-1]
      attrs.zip(init_args) do |attr, arg|
        obj.instance_variable_set "@#{attr}", arg
      end
      obj.send :initialize, *surplus_args
      obj
    end
  end
end

您现在可以这样做:

class MyClass < ParentClass
  initialize_with :foo, :bar
  def initialize(baz)
    @initialized = true
    super(baz) # pass any arguments to initializer of superclass
  end
end
my_obj = MyClass.new "foo", "bar", "baz"
my_obj.foo #=> "foo"
my_obj.bar #=> "bar"
my_obj.instance_variable_get(:@initialized) #=> true

此解决方案的一些特点:

  • initialize_with 指定构造函数属性
  • 可选择使用initialize 进行自定义初始化
  • 可以在initialize 中拨打super
  • initialize 的参数是initialize_with 指定的属性未使用的参数
  • 轻松提取到模块中
  • 使用initialize_with 指定的构造函数属性被继承,但在子类上定义新集合将删除父属性
  • 动态解决方案可能会影响性能

如果您想创建一个性能开销绝对最小的解决方案,将大部分功能重构为一个字符串,在定义初始化程序时可以evaled 并不难.我没有对差异进行基准测试。

注意:我发现破解 new 比破解 initialize 效果更好。如果您使用元编程定义 initialize,您可能会遇到这样一种情况:您将一个块传递给 initialize_with 作为替代初始化程序,并且不可能在一个块中使用 super

【讨论】:

  • 这个解决方案看起来不错。似乎您可以调用 zsuper(没有括号或参数的 super),即 super 而不是 super(baz)。我肯定希望看到一个不需要define_method 并且可以避免使用instance_variable_set 的解决方案。正如您所指出的,这可能需要 String eval。
  • 我接受这个解决方案,即使它使用了define_method。明天我会在下面提交我自己的答案供大家考虑。
【解决方案2】:

这是我想到的第一个解决方案。我的模块有一个很大的缺点:您必须在包含模块之前定义类初始化方法,否则它将无法工作。

对于这个问题可能有更好的解决方案,但这是我在不到几分钟的时间内写的。

另外,我没有过多考虑表演。您可能会找到比我更好的解决方案,尤其是谈论表演。 ;)

#!/usr/bin/env ruby -wKU

require 'rubygems'
require 'activesupport'


module Initializable

  def self.included(base)
    base.class_eval do
      extend  ClassMethods
      include InstanceMethods
      alias_method_chain :initialize, :attributes
      class_inheritable_array :attr_initializable
    end
  end

  module ClassMethods

    def attr_initialized(*attrs)
      attrs.flatten.each do |attr|
        attr_accessor attr
      end
      self.attr_initializable = attrs.flatten
    end

  end

  module InstanceMethods

    def initialize_with_attributes(*args)
      values = args.dup
      self.attr_initializable.each do |attr|
        self.send(:"#{attr}=", values.shift)
      end
      initialize_without_attributes(values)
    end

  end

end


class MyClass1
  attr_accessor :foo, :bar

  def initialize(foo, bar)
    @foo, @bar = foo, bar
  end
end

class MyClass2

  def initialize(*args)
  end

  include Initializable

  attr_initialized :foo, :bar
end


if $0 == __FILE__
  require 'test/unit'

  class InitializableTest < Test::Unit::TestCase

    def test_equality
      assert_equal MyClass1.new("foo1", "bar1").foo, MyClass2.new("foo1", "bar1").foo
      assert_equal MyClass1.new("foo1", "bar1").bar, MyClass2.new("foo1", "bar1").bar
    end

  end
end

【讨论】:

  • 对于 ActiveSupport 依赖项,我几乎会立即拒绝此解决方案。对这类事情的任何依赖都是不可取的,但是需要周围最令人发指的 Ruby 库只会雪上加霜。
  • 我使用 ActiveSupport 只是为了利用 alias_method_chain 但您根本不需要它。您可以使用别名。
【解决方案3】:
class MyClass < Struct.new(:foo, :bar)
end

【讨论】:

  • 这个解决方案的问题是,如果你添加一个带参数的初始化器,它会擦除​​默认的 Struct 初始化器。
  • 您仍然可以执行以下操作: class MyClass
  • 我喜欢 class MyClass
  • 我发现从 Struct.new() 继承会破坏 Rails 代码自动重新加载,还会导致 TypeErrors:stackoverflow.com/questions/9785694/…
【解决方案4】:

我知道这是一个老问题,答案完全可以接受,但我想发布我的解决方案,因为它利用了Module#prepend(Ruby 2.2 中的新功能)以及模块也是非常简单解决方案的类这一事实。首先是制作魔法的模块:

class InitializeWith < Module
  def initialize *attrs
    super() do
      define_method :initialize do |*args|
        attrs.each { |attr| instance_variable_set "@#{attr}", args.shift }
        super *args
      end
    end
  end
end

现在让我们使用我们花哨的模块:

class MyClass
  prepend InitializeWith.new :foo, :bar
end

请注意,我留下了 attr_accessible 的东西,因为我认为这是一个单独的问题,尽管支持它是微不足道的。现在我可以创建一个实例:

MyClass.new 'baz', 'boo'

我仍然可以为自定义初始化定义一个initialize。如果我的自定义 initialize 接受一个参数,那么这些参数将是提供给新实例的任何额外参数。所以:

class MyClass
  prepend InitializeWith.new :foo, :bar

  def initialize extra
    puts extra
  end
end
MyClass.new 'baz', 'boo', 'dog'

在上面的例子中@foo='baz'@bar='boo',它将打印dog

我还喜欢这个解决方案的一点是它不会使用 DSL 污染全局命名空间。需要此功能的对象可以prepend。其他人都没有受到影响。

【讨论】:

    【解决方案5】:

    此模块允许将 attrs 哈希作为 new() 的选项。您可以将模块包含在具有继承的类中,并且构造函数仍然有效。

    我更喜欢将 attr 值列表作为参数,因为,尤其是继承的 attrs,我不想记住哪个参数是哪个。

    module Attrize
      def initialize(*args)
        arg = args.select{|a| a.is_a?(Hash) && a[:attrs]}
        if arg
          arg[0][:attrs].each do |key, value|
            self.class.class_eval{attr_accessor(key)} unless respond_to?(key)
            send(key.to_s + '=', value)
          end
          args.delete(arg[0])
        end
        (args == []) ? super : super(*args)
      end
    end
    
    class Hue
      def initialize(transparent)
        puts "I'm transparent" if transparent
      end
    end
    
    class Color < Hue
      include Attrize
      def initialize(color, *args)
        p color
        super(*args)
        p "My style is " + @style if @style
      end
    end
    

    你可以这样做:

    irb(main):001:0> require 'attrize'
    => true
    irb(main):002:0> c = Color.new("blue", false)
    "blue"
    => #<Color:0x201df4>
    irb(main):003:0>  c = Color.new("blue", true, :attrs => {:style => 'electric'})
    "blue"
    I'm transparent
    "My style is electric"
    

    【讨论】:

      猜你喜欢
      • 2015-07-16
      • 1970-01-01
      • 1970-01-01
      • 2018-03-09
      • 2011-11-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多