Ruby - как вызвать одну и ту же ошибку для нескольких методов, не записывая ее несколько раз?

Допустим, я создаю класс Calculator, который работает, манипулируя элементами массива — в этом классе я определяю несколько методов: сложение, вычитание, умножение, деление. Я хочу, чтобы каждый метод вызывал одну и ту же ошибку, если в массиве существует только 1 или меньше элементов, например:

class Calculator
# ...
def add
  if @array.length < 2
    raise 'Not Enough Elements'
  else
    @array << @array.pop + @array.pop
  end
end
# ...
end

Я мог бы написать условие для возникновения ошибки в каждом методе, но это кажется очень утомительным и не-Ruby. Будет ли способ применить возникшую ошибку ко всем методам, которые в ней нуждаются, чтобы сохранить весь этот ввод?


person Alex Kornfeld    schedule 19.11.2016    source источник
comment
Что происходит при использовании унарной или n-арной операции? :‹   -  person user2864740    schedule 20.11.2016


Ответы (3)


Одним из вариантов было бы перемещение логики проверки длины в собственный метод и использование ее там, где это необходимо:

class Calculator
  def add
    check_array_length
    # rest of the method
  end

  private

  def check_array_length
    raise 'Not Enough Elements' if @array.length < 2
  end
end

Если вы устанавливаете @array в методе initialize, вы можете сделать рейз на ранней стадии, заявив, что не можете продолжить из-за слишком малого количества элементов в @array:

class Calculator
  def initialize(array)
    raise 'Not Enough Elements' if array.length < 2

    @array = array
  end
end
person Andrey Deineko    schedule 19.11.2016

Вот возможная структура:

module Validator
  [:add, :substract, :multiply, :divide].each do |method|
    define_method(method) do
      validate_array_length(2)
      super()
    end
  end

  private

  def validate_array_length(min,max=min)
    raise 'Not Enough Elements' if @array.length < min
    raise 'Too Many Elements' if @array.length > max
  end
end

class Calculator
  prepend Validator
  def initialize(*values)
    @array = values
  end

  def add
    @array << @array.pop + @array.pop
  end

  # def substract ....
end

c = Calculator.new(3,2)
c.add
c.add
# => calculator.rb:12:in `validate_array_length': Not Enough Elements (RuntimeError)
person Eric Duminil    schedule 19.11.2016

class Calculator
  def initialize(arr)
    @arr = arr
  end

  def add;      binary(:+);    end
  def subtract; binary(:-);    end
  def multiply; binary(:*);    end
  def divide;   binary(:/);    end
  def power;    binary(:**);   end
  def modulo;   binary(:%);    end
  # ... (others)

  def negate;   unary(:-@);    end
  def odd?;     unary(:odd?);  end
  def even?;    unary(:even?); end
  def to_f;     unary(:to_f);  end
  # ... (others)

  private

  def binary(op)
    raise ArgumentError, 'Too few elements' if @arr.length < 2
    @arr.pop.send(op, @arr.pop)
  end

  def unary(op)
    raise ArgumentError, 'Too few elements' if @arr.length.zero?
    @arr.pop.send(op)
  end
end

#                      add  neg   mod   pow   div  mult     sub     add 
calc = Calculator.new [  1,   5,  2,3,  4,5,  6,7,  8,9,  10,11,  12,13]
  #=> #<Calculator:0x007fa192030968 @arr=[1, 5, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]> 

calc.add       #=> 25  (13+12) 
calc.subtract  #=> 1   (11-10) 
calc.multiply  #=> 72  (8*9)   
calc.divide    #=> 1   (7/6)
calc.power     #=> 625 (5**4)
calc.modulo    #=> 1   (3%2)
calc.negate    #=> -5  (5)
calc.add       #=> ArgumentError: Too few elements

It appears that you are constructing an RPN calculator. If so, you probably want to push the result of each calculation back onto the stack. For binary operators you could change the method binary as follows:

  def binary(op)
    raise ArgumentError, 'Too few elements' if @arr.length < 2
    @arr << @arr.pop.send(op, @arr.pop)
    @arr[-1]
  end

@arr[-1], результат вычисления, является возвращаемым значением. Модификация unary аналогична.

Вы можете добавить некоторые методы управления стеком, такие как

def pop
  @arr.pop
end

def push(n)
  @arr << n
end

def swap
  @arr[-1], @arr[-2] = @arr[-2], @arr[-1]
end

def rotate
  @arr.rotate
end

Наконец, вам может быть проще сделать начало (а не конец) @arr вершиной стека, в котором вы будете использовать unshift/shift, а не push/pop.

person Cary Swoveland    schedule 20.11.2016