web-dev-qa-db-fra.com

Dans Ruby, quelle est la relation entre «nouveau» et «initialiser»? Comment retourner nil lors de l'initialisation?

Ce que je veux c'est:

obj = Foo.new(0)  # => nil or false

Cela ne fonctionne pas:

class Foo
  def initialize(val)
    return nil if val == 0
  end
end

Je sais qu'en C/C++/Java/C #, nous ne pouvons pas retourner une valeur dans un constructeur.

Mais je me demande si c'est possible en Ruby.

48
Chris Xue

Dans Ruby, quelle est la relation entre 'new' et 'initialize'?

new appelle généralement initialize. L'implémentation par défaut de new est quelque chose comme:

class Class
  def new(*args, &block)
    obj = allocate

    obj.initialize(*args, &block)
    # actually, this is obj.send(:initialize, …) because initialize is private

    obj
  end
end

Mais vous pouvez, bien sûr, le remplacer pour faire tout ce que vous voulez.

Comment retourner nil lors de l'initialisation?

Ce que je veux c'est:

obj = Foo.new(0)  # => nil or false

Cela ne fonctionne pas:

class Foo
  def initialize(val)
    return nil if val == 0
  end
end

Je sais qu'en C/C++/Java/C #, nous ne pouvons pas retourner une valeur dans un constructeur.

Mais je me demande si c'est possible en Ruby.

Il n'y a pas de constructeur en Ruby. Dans Ruby, il n'y a que des méthodes et elles peuvent renvoyer des valeurs.

Le problème que vous voyez est simplement que vous souhaitez modifier la valeur de retour d'une méthode mais que vous remplacez une autre méthode. Si vous souhaitez modifier la valeur de retour de la méthode bar, vous devez remplacer bar, pas une autre méthode.

Si vous souhaitez modifier le comportement de Foo::new, vous devez alors modifier Foo::new:

class Foo
  def self.new(val)
    return nil if val.zero?
    super
  end
end

Notez, cependant, qu'il s'agit d'un très mauvaise idée, car il viole le contrat de new, qui consiste à renvoyer une instance pleinement initialisée et pleinement fonctionnelle de la classe.

79
Jörg W Mittag

Il existe des différences importantes entre les deux méthodes.

new est une méthode class, qui crée généralement une instance de la classe (cela traite des choses délicates comme l'allocation de mémoire dont Ruby vous protège de vous n'avez donc pas à vous salir trop).

Ensuite, initialize, une méthode instance, indique à l'objet de définir son état interne en fonction des paramètres demandés.

Ces deux options peuvent être remplacées en fonction de ce que vous souhaitez. Par exemple, Foo.new Peut réellement créer et renvoyer une instance de FooSubclass si elle doit être suffisamment intelligente pour le faire.

Cependant, il est souvent préférable de déléguer des cas d'utilisation comme ceux-ci à d'autres méthodes de classe qui sont plus explicites sur ce qu'elles font, par exemple Foo.relating_to(bar). Briser les attentes des autres sur ce que des méthodes comme new devraient faire va dérouter les gens plus que cela ne les aidera à long terme.

Par exemple, regardez l'implémentation de Singleton, un module qui permet à une seule instance d'une classe particulière d'exister. Il rend la méthode new privée et expose une méthode instance qui retourne l'instance existante de l'objet ou appelle new si elle n'a pas encore été créée.

45
Gareth

Vous pouvez quelque chose comme ça:

class Foo

  def self.init(val)
    new(val) unless val == 0
  end

  def initialize(val)
    #...
  end
end

Exemple d'utilisation:

obj = Foo.init(0)
 => nil
obj = Foo.init(5)
 => #<Foo:0x00000002970a98>
5
Flexoid

Vouloir faire

class Foo
  def initialize(val)
    return nil if val == 0
  end
end

rendrait le code incohérent.

Si tu avais

class Foo
  def initialize(val)
    return nil if val == 0
    @val = val
    @bar = 42
  end
end

que voudriez-vous récupérer si vous faisiez Foo.new(1)? Souhaitez-vous 42 (La valeur de retour pour Foo#initialize), Ou un objet foo? Si vous voulez un objet foo pour Foo.new(1), alors pourquoi vous attendriez-vous à ce que return nil Fasse en sorte que Foo.new(0) renvoie zéro?

4
Andrew Grimm