How to break down an array within an superclass ob

2019-03-01 14:50发布

问题:

Let's say I have two classes:

class One
  attr_reader :array
  def initialize(array)
    @array = array
  end
end

class Two < One
  attr_reader :array
  def initialize
    @array = []
  end
end

I now instantiate one object of class "One" and two objects from class "Two".

array = [1,2] 
a = One.new(array)
b = Two.new
c = Two.new

Is it possible to break apart the @array array stored in "a" and place the two elements in "b" and "c" such that:

if element is odd, place it in b.array.
if element is even, place it in c.array.

Output:
  b.array = [1]
  c.array = [2]

I know this might be legal Ruby as I'm learning how inheritance works...

回答1:

You can use Enumerable#partition:

array = [1, 2, 3, 4]
array.partition { |x| x % 2 == 1 }
# => [[1, 3], [2, 4]]
odd, even = array.partition {|x| x % 2 == 1}
odd
# => [1, 3]
even
# => [2, 4]

class One
  attr_reader :array
  def initialize(array)
    @array = array
  end
end

class Two < One
  attr_reader :array
  def initialize
    @array = []
  end
end

array = [1,2] 
a = One.new(array)
b = Two.new
c = Two.new

odd, even = array.partition {|x| x % 2 == 1}
b.array.concat odd
c.array.concat even
b.array
# => [1]
c.array
# => [2]

NOTE: As meagar commented, the problem is not related with inheritance. You don't need to use class here at all.



回答2:

TL;DR

You don't actually need multiple objects here. In fact, you don't even really need a custom class. If you do use a class, one typical Ruby idiom for this use case would be to pass a block to your getter method. Alternatively, you can define your own custom getter to take an argument.

All Methods Take a Block

In Ruby, all methods implicitly take a block as the last argument. Consider the following example:

class One
  attr_reader :array
  def initialize array
    @array = array
  end
end

a = One.new [*1..5]

a.array
#=> [1, 2, 3, 4, 5]

a.array.select { |e| e.odd?  }
#=> [1, 3, 5]

a.array.select { |e| e.even? }
#=> [2, 4]

You can get the behavior you want (e.g. different views of your array data) with only a single class, but still have access to the whole data set in stored in @array if you need it.

Arguments to Getters

If you want to store the behavior in the class and provide an interface, one way to do this is to provide an explicit getter method yourself rather than letting Ruby make one for you with #attr_reader. For example, by providing an optional argument to One#array, you can control whether you get the whole array or a subset. Consider the following:

class One
  def initialize array
    @array = array
  end

  def array mod=nil
    mod ? @array.select { |e| e.modulo(mod).zero? } : @array
  end
end

a = One.new [*1..10]

a.array
#=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

a.array 2
#=> [2, 4, 6, 8, 10]

a.array 5
#=> [5, 10]

As long as your objects have a well-defined interface, both options (as well as others) are all equally valid. It really just depends on what you're trying to express with your code, and how flexible your code needs to be.