3
votes

Ruby, classe et héritage

J'ai besoin d'aide pour comprendre l'héritage.

class MyArray < Array
end

a = MyArray[1, 2, 3] #=> [1, 2, 3]
b = MyArray[4, 5]    #=> [4, 5]
c = a + b            #=> [1, 2, 3, 4, 5]

a.class #=> MyArray
b.class #=> MyArray
c.class #=> Array

Je ne comprends pas pourquoi le résultat de l'ajout n'est pas une instance de la classe MyArray .


3 Réponses :


5
votes

Je ne comprends pas pourquoi mon tableau "a" n'est pas la classe "MyArray" après l'ajout.

Pourquoi devrait-il (être un MyArray )? L'opération de concaténation sur les tableaux est définie pour renvoyer un nouveau Array , c'est donc ce qui se passe ici. https://ruby-doc.org/core-2.5 .3 / Array.html # method-i-2B

Si vous le souhaitez, vous pouvez remplacer cette opération dans votre classe pour renvoyer une instance de MyArray. N'oubliez pas toutes les autres méthodes similaires.

C'est aussi pourquoi c'est une mauvaise idée de sous-classer les collections standard. Mieux vaut utiliser la composition plutôt que l'héritage ici.


10 commentaires

OK, je comprends. Mais je souhaite définir d'autres méthodes sur mes objets MyArray. Comment puis-je faire ça?


@ µgeek: que voulez-vous dire? Tout comme vous définissez n'importe quelle autre méthode.


Oui, je veux définir une méthode, par exemple pour comparer 2 tableaux, ou couper un tableau à deux autres tableaux ...


Mais je veux aussi pouvoir utiliser les méthodes des tableaux


Dois-je seulement insérer des méthodes dans la classe Array?


@ µgeek Je ne sais pas ce que vous vouliez dire par là, mais il semble que vous bénéficierez de continuer avec le tutoriel / livre / tout ce que vous utilisez et d'essayer des exercices plus simples pour l'instant (ceux qui n'impliquent pas de sous-classes de classes standard). Si ma mémoire est bonne, le livre de pioche a utilisé une excellente application de bibliothèque de chansons comme exemples.


Merci pour votre conseil. Je vais essayer des exercices plus simples ... Est-ce vraiment une mauvaise façon d'ajouter des méthodes à une classe standard?


@ µgeek: oui, vous devriez vous abstenir de faire cela, jusqu'à ce que vous puissiez expliquer pourquoi vous en avez besoin et pourquoi d'autres approches ne sont pas assez bonnes.


"L'opération de concaténation est définie pour renvoyer un nouveau tableau" - la documentation dit simplement "tableau" (minuscules), la classe n'est pas spécifiée. Et d'autres méthodes comme a [1..2] ou a * 2 qui retournent également un "nouveau tableau" conservent en fait la classe du receveur, c'est-à-dire qu'elles renvoient Instances MyArray . "Pourquoi devrait-il s'agir d'un MyArray?" - car ce serait moins surprenant. Je pense que la conversion en Array est due à l'optimisation plutôt qu'à la considération de la POO.


"un [1..2] ou un * 2 ... retourne des instances MyArray" - oh wow, je ne m'y attendais absolument pas.



3
votes

Juste pour ajouter un peu à la réponse de Sergio en termes de son commentaire sur l'utilisation de la composition par rapport à l'héritage et de l'échange dans les commentaires.

Au lieu de dire MyArray est un tableau, vous pouvez dire MyArrayLike a et array. Ensuite, vous pouvez "transférer" les méthodes qui ont du sens vers le tableau sous-jacent, tout en ajoutant vos propres fonctionnalités qui ont du sens pour votre classe sans tableau de sous-classification.

Ruby en a même plusieurs façons de rendre cela très facile, y compris le Forwardable a > module.

class MyArrayLike 
   attr_reader :arr 
   def initialize( initial_arr )
      @arr = initial_arr 
   end

   def +(other)
     result = self.class.new(arr + other.arr)
     # maybe you want to do more than just concat the underlying array, if so you can do it here
     result
   end

   def first
      # for example maybe you want first to just return the first item in the underlying array.
      arr.first 
   end    
end


a = MyArrayLike.new([1,2,3])
b = MyArrayLike.new([4,5])

puts "a.class = #{a.class}"
# => a.class = MyArrayLike
puts a
# => #<MyArrayLike:0x00000000dc4b00>
a += b
puts "a.class = #{a.class}"
# => a.class = MyArrayLike
puts a 
# => #<MyArrayLike:0x00000000dc4470>

puts a.first 
# => 1
puts a.arr 
# => 1
#    2
#    3
#    4
#    5 


0 commentaires

3
votes

Ajouter MyArray à MyArray pour obtenir Array peut être contre-intuitif, mais une méthode peut être définie pour renvoyer n'importe quelle classe. Et dans le cas de Array # + , que vous appelez, il se trouve qu'il est simplement défini pour renvoyer un Array . C'est tout.

Si vous voulez qu'il renvoie un MyArray , une façon de le faire est de définir MyArray # + comme suit:

class MyArray < Array
  def +other
    MyArray.new(super)
  end
end

(MyArray.new([1, 2, 3]) + MyArray.new([4, 5])).class # => MyArray

En passant, notez que votre définition de MyArray # initialize n'a pas de sens, et donc redondante.


1 commentaires

Implémentation alternative: dup.concat (autre)