在类初始化方法中处理self的数组

时间:2014-10-13 09:59:28

标签: ruby arrays initialization self

我们说我定义了类:

Class Product < Struct.new(:field1,:field2)

以及相应的new方法,它将输入string解析为此类的属性:

def initialize(string)
    splitted=string.split(',')
    @field1=splitted[0]
    @field2=splitted[1]
end

我希望使用相同的new方法来解析其他类,包括Product以及响应to_s的所有内容:

def initialize(string)
    case
    when string.is_a?(String)
        splitted=string.split(',')
        @field1=splitted[0]
        @field2=splitted[1]
    when string.is_a?(Product)
        @field1,@field2=string.deal
    when string.respond_to?(:to_s)
        @field1,@field2=Product.new(string.to_s).deal
    else
        raise RuntimeError,"Cannot understand product '#{string}' of class '#{string.class}'.", caller
    end
end

def deal
    return @field1,@field2
end

现在我也想要这个新方法来处理数组:

productarray=Product.new(["some field,another field","name, surname",:"up, down",Product.new("something, anything")])

我最初的猜测是在when方法中添加另一个new子句:

when string.is_a?(Array)
    return string.map{|s| Product.new(s)}

但这并不奏效。

问题:

  • 如何使用initialize类方法来处理self的数组(如上所述)?
  • 它似乎并非如此&#34; ruby​​ish&#34;在case中使用when / new结构来处理每个不同的类;还有其他选择吗?
  • 为什么字符串是一个产品,我无法改变自我的价值并使用更简单的代码self=string并且必须经历deal的所有麻烦?< / LI>

参考文献:

Ruby: Array of Objects/Classes

2 个答案:

答案 0 :(得分:1)

您需要做出一系列设计决策。首先,使用Adapter模式将“Product”或“String”转换为Product类可识别的args列表:

class Product < Struct.new(:field1,:field2)
  def deal
    return field1, field2
  end
end


class StringToFieldsAdapter < Struct.new(:string)
  def run
   string.split(',')
  end
end

class ProductToFieldsAdapter < Struct.new(:product)
  def run
    product.deal
  end
end

让我们测试一下这些适配器:

string_adapter = StringToFieldsAdapter.new('hey,there').run #=> [hey, there]
product_adapter = ProductToFieldsAdapter.new(Product.new(*StringToFieldsAdapter.new('one,two').run)).run #=> [one, two]
product_from_string = Product.new(*string_adapter) # the splat * just turns the [hey, there] array into arguments
product_from_product = Product.new(*product_adapter) #=> #<struct Product field1="one", field2="two">

现在,在您使用数组的示例中,您并不真正想要在产品中接受数组。你想要一些接受数组并从每个元素中创建一个新对象的方法。

为此,我创建了一个类,它将识别要使用的适配器,然后是另一个实际创建对象的类(是工厂):

arr = ["some field,another field","name, surname","up, down",Product.new(*StringToFieldsAdapter.new("something, anything").run)]

class AdapterRecognizer < Struct.new(:object)
  def run
    case object
      when String then StringToFieldsAdapter
      when Product then ProductToFieldsAdapter
    end
  end
end


class ProductFactory < Struct.new(:array)
  def create_objects_from_each_array_element
    array.map do |element|
      adapter_to_use = AdapterRecognizer.new(element).run
      Product.new(*adapter_to_use.new(element).run)
    end
  end
end

p ProductFactory.new(arr).create_objects_from_each_array_element #=> should produce bunch of products

我只为字符串和产品制作了适配器,您可以轻松地为符号等扩展。例如,添加符号只是添加新适配器类的简单实例:

class SymbolToFieldsAdapter < Struct.new(:symbol)
  def run
    symbol.to_s.split(',').map(&:to_sym)
  end
end

然后在适配器识别器中添加另一个'when':

when Symbol then SymbolToFieldsAdapter

答案 1 :(得分:0)

问题在于when的顺序。由于ruby中的所有内容都响应to_s,尤其是Array,因此您的数组对象会在最后一个语句之前被捕获并尝试将其作为字符串处理。 编辑:问题是您无法从初始化程序中有意义地返回,您可以使用它作为一种方法来阻止程序运行以下语句,但.new不会使用您返回的内容。另一方面,您可以使用类方法为您构建对象列表,如下所示。然而,为此目的建立工厂更好,但这也有效。

话虽如此,正如@daremkd所提到的,做这件事并不是一个非常好的设计决定。既然你处理了所有这些特殊情况,你就会在未来将代码打开到很多可能的错误中,更重要的是使这个方法的心理模型变得非常复杂。

class Product
  def initialize(field1, field2)
    @field1 = field1
    @field2 = field2
  end

  def self.new_from_parameters(string)
    case 
    when string.is_a?(String)
      splitted=string.split(',')
      Product.new(splitted[0],splitted[1])
    when string.is_a?(Product)
      string
    when string.respond_to?(:to_s)
      Product.new_from_parameters(string.to_s)
    else
      raise RuntimeError,"Cannot understand product '#{string}' of class '#{string.class}'.", caller
    end
  end
end