Как изменить возвращаемое значение конструктора класса в Ruby?

У меня есть класс Foo. Я хочу иметь возможность передать конструктору экземпляр Foo, foo и вернуть тот же экземпляр.

Другими словами, я хочу пройти этот тест:

class Foo; end

foo = Foo.new
bar = Foo.new(foo)

assert_equal foo, bar

Кто-нибудь знает, как я могу это сделать? Я пробовал это:

class Foo
  def initialize(arg = nil)
    return arg if arg
  end
end

foo = Foo.new
bar = Foo.new(foo)

assert_equal foo, bar  # => fails

но это не сработает.

Справка

ИЗМЕНИТЬ

Потому что многие люди просили мое обоснование:

Я делаю быстрый анализ большого количества данных (много TB), и у меня будет много примеров множества объектов. Для некоторых из этих объектов нет смысла иметь два разных экземпляра с одними и теми же данными. Например, одним из таких объектов является объект "окно" (как в временном окне), который имеет два свойства: время начала и время окончания. Я хочу иметь возможность использовать конструктор любым из этих способов и вернуть объект окна:

window = Window.new(time_a, time_b)
window = Window.new([time_a, time_b])
window = Window.new(seconds_since_epoch_a, seconds_since_epoch_b)
window = Window.new(window_obj)
window = Window.new(end => time_b, start => time_a)
...

Некоторый другой объект, который нуждается в окне, может быть создан таким образом:

obj = SomeObj.new(data => my_data, window => window_arg)

Я не обязательно знаю, что в window_arg, и мне все равно - он примет любой единственный аргумент, который может быть интерпретирован конструктором Window. Если у вас уже есть экземпляр Window, я бы скорее использовал этот экземпляр. Но работа по интерпретации выглядит как проблема конструктора Window. Во всяком случае, как я уже упоминал, я собираю много TB данных и создаю много примеров вещей. Если объект окна передается, я хочу, чтобы он просто был распознан как объект окна и использовался.

Ответ 1

def Foo.new(arg=nil)
  arg || super
end

Ответ 2

В definition конструкторы предназначены для возврата вновь созданного объекта класса, членом которого они являются, поэтому, вы не должны не переопределять это поведение.

Кроме того, в Ruby new вызывает initialize где-то внутри своего тела метода, и его возвращаемое значение игнорируется, поэтому в любом случае значение, возвращаемое с initialize, не возвращается из new.

С учетом сказанного, я думаю, что в вашем случае вам может понадобиться создать метод factory, который будет возвращать разные объекты Foo на основе аргументов, переданных методу factory:

class Foo
  def self.factory(arg = nil)
    return arg if arg.kind_of? Foo
    Foo.new
  end
end
foo = Foo.factory
bar = Foo.factory(foo)

assert_equal foo, bar #passes

Ответ 3

initialize вызывается new, который игнорирует его возвращаемое значение. В основном метод по умолчанию new выглядит так (за исключением того, что он реализован в C, а не в ruby):

class Class
  def new(*args, &blk)
    o = allocate
    o.send(:initialize, *args, &blk)
    o
  end
end

Таким образом, вновь выделенный объект возвращается в любом случае, независимо от того, что вы делаете в initialize. Единственный способ изменить это - переопределить метод new, например:

class Foo
  def self.new(arg=nil)
    if arg
      return arg
    else
      super
    end
  end
end

Однако я бы настоятельно советовал против этого, так как он противоречит многим ожиданиям, которые люди имеют при вызове new:

  • Люди ожидают, что new вернет новый объект. Я имею в виду, что он даже называется new. Если вы хотите использовать метод, который не всегда создает новый объект, вы, вероятно, должны называть его чем-то другим.
  • По крайней мере, люди ожидают, что Foo.new вернет объект Foo. Ваш код вернет все аргументы. То есть Foo.new(42) вернет 42, Integer, а не объект Foo. Поэтому, если вы собираетесь это сделать, вы должны хотя бы вернуть этот объект, если это объект Foo.

Ответ 4

Не работает:


class Some
    def self.new( str )
        SomeMore.new( str )
    end
end

# the Some is parent of SomeMore class SomeMore < Some def initialize( str ) @str = str end end

код >

Ответ 5

Для этого конкретного случая использования может быть лучше использовать один из этих подходов.

class Foo
    def self.new(args=nil)
        @@obj ||= super(args)
    end
end


class Foo
    def self.new(args)
      @@obj = super(args)
    end

    def self.new
      @@obj
    end

end

Это позволяет вам иметь только один объект, который создается, который может использоваться универсально, но возвращает объект класса Foo, что делает его более подходящим для стандартных ожиданий метода new, как указал Джейкоб вне.

Ответ 6

Я понял это случайно, и это работает слишком хорошо. скажем, вы хотите, чтобы конструктор (по какой-либо причине) возвращал число с плавающей точкой:

class Decimal
   # possibly a bug that this works
   @@value =
   def self.new(input)
      if input.class == Integer
         @@value = input.to_f
      elsif input.class == Float
         @@value = input
      else
         @@value = -1.0
      end
   end
end
x = Decimal.new(4)
y = Decimal.new(32.141)
puts x # 4.0
puts y # 32.141

Или в твоем случае, Foo:

class Foo
   @@value = 
   def self.new(args)

      # some code ...

      @@value = Foo.new #:<= your return value goes here
   end
end