Come si fa il polimorfismo in Ruby?
-
02-07-2019 - |
Domanda
In C #, posso farlo:
class Program
{
static void Main(string[] args)
{
List<Animal> animals = new List<Animal>();
animals.Add(new Dog());
animals.Add(new Cat());
foreach (Animal a in animals)
{
Console.WriteLine(a.MakeNoise());
a.Sleep();
}
}
}
public class Animal
{
public virtual string MakeNoise() { return String.Empty; }
public void Sleep()
{
Console.Writeline(this.GetType().ToString() + " is sleeping.");
}
}
public class Dog : Animal
{
public override string MakeNoise()
{
return "Woof!";
}
}
public class Cat : Animal
{
public override string MakeNoise()
{
return "Meow!";
}
}
Ovviamente, l'output è (Leggermente parafrasato):
- Woof
- Il cane dorme
- Meow
- Il gatto dorme
Dato che C # viene spesso deriso per la sua sintassi di tipo prolisso, come gestisci i metodi polimorfismo / virtuali in un linguaggio tipizzato come anatra come Ruby?
Soluzione
modifica: aggiunto altro codice per la domanda aggiornata
disclaimer: non uso Ruby da circa un anno e non l'ho installato su questa macchina, quindi la sintassi potrebbe essere completamente sbagliata. Ma i concetti sono corretti.
Esattamente allo stesso modo, con classi e metodi sostituiti:
class Animal
def MakeNoise
return ""
end
def Sleep
print self.class.name + " is sleeping.\n"
end
end
class Dog < Animal
def MakeNoise
return "Woof!"
end
end
class Cat < Animal
def MakeNoise
return "Meow!"
end
end
animals = [Dog.new, Cat.new]
animals.each {|a|
print a.MakeNoise + "\n"
a.Sleep
}
Altri suggerimenti
Tutte le risposte finora mi sembrano abbastanza buone. Pensavo di menzionare che l'intera eredità non è del tutto necessaria. Escludendo il "sonno" comportamento per un momento, possiamo ottenere l'intero risultato desiderato usando la tipizzazione delle anatre e omettendo la necessità di creare una classe base di animali. Cercando su Google "digitando anatra" dovrebbe fornire un numero qualsiasi di spiegazioni, quindi per questo diciamo solo "se cammina come un'anatra e cigola come un'anatra ..."
Il " sleep " il comportamento potrebbe essere fornito utilizzando un modulo mixin, come Array, Hash e altre classi integrate di Ruby, incluso Enumerable. Non sto suggerendo che sia necessariamente migliore, solo un modo diverso e forse più idiomaticamente rubino di farlo.
module Animal
def sleep
puts self.class.name + " sleeps"
end
end
class Dog
include Animal
def make_noise
puts "Woof"
end
end
class Cat
include Animal
def make_noise
puts "Meow"
end
end
Conosci il resto ...
Uso del rubino idiomatico
class Animal
def sleep
puts "#{self.class} is sleeping"
end
end
class Dog < Animal
def make_noise
"Woof!"
end
end
class Cat < Animal
def make_noise
"Meow!"
end
end
[Dog, Cat].each do |clazz|
animal = clazz.new
puts animal.make_noise
animal.sleep
end
Partendo dalla risposta precedente, è così che potresti farlo?
Secondo taglio dopo il chiarimento:
class Animal
def MakeNoise
raise NotImplementedError # I don't remember the exact error class
end
def Sleep
puts self.class.to_s + " is sleeping."
end
end
class Dog < Animal
def MakeNoise
return "Woof!"
end
end
class Cat < Animal
def MakeNoise
return "Meow!"
end
end
animals = [Dog.new, Cat.new]
animals.each {|a|
puts a.MakeNoise
a.Sleep
}
(Lascerò così com'è, ma " self.class.name " vince su " .to_s ")
Il principio della tipizzazione anatra è proprio che l'oggetto deve rispondere ai metodi chiamati. Quindi qualcosa del genere potrebbe fare anche il trucco:
module Sleeping
def sleep; puts "#{self} sleeps"
end
dog = "Dog"
dog.extend Sleeping
class << dog
def make_noise; puts "Woof!" end
end
class Cat
include Sleeping
def to_s; "Cat" end
def make_noise; puts "Meow!" end
end
[dog, Cat.new].each do |a|
a.sleep
a.make_noise
end
Una piccola variante della soluzione di manveru che crea dinamicamente diversi tipi di oggetti basati su una matrice di tipi di classe. Non molto diverso, solo un po 'più chiaro.
Species = [Dog, Cat]
Species.each do |specie|
animal = specie.new # this will create a different specie on each call of new
print animal.MakeNoise + "\n"
animal.Sleep
end
Ecco come lo scriverei:
class Animal
def make_noise; '' end
def sleep; puts "#{self.class.name} is sleeping." end
end
class Dog < Animal; def make_noise; 'Woof!' end end
class Cat < Animal; def make_noise; 'Meow!' end end
[Dog.new, Cat.new].each do |animal|
puts animal.make_noise
animal.sleep
end
Non è veramente diverso dalle altre soluzioni, ma questo è lo stile che preferirei.
Sono 12 linee contro le 41 linee (in realtà, puoi radere via 3 linee usando un inizializzatore di raccolta) dall'esempio C # originale. Non male!
Esiste un metodo diventa
che implementa un polimorfismo (copiando tutte le variabili di istanza da una data classe a una nuova)
class Animal
attr_reader :name
def initialize(name = nil)
@name = name
end
def make_noise
''
end
def becomes(klass)
became = klass.new
became.instance_variables.each do |instance_variable|
value = self.instance_variable_get(instance_variable)
became.instance_variable_set(instance_variable, value)
end
became
end
end
class Dog < Animal
def make_noise
'Woof'
end
end
class Cat < Animal
def make_noise
'Meow'
end
end
animals = [Dog.new('Spot'), Cat.new('Tom')]
animals.each do |animal|
new_animal = animal.becomes(Cat)
puts "#{animal.class} with name #{animal.name} becomes #{new_animal.class}"
puts "and makes a noise: #{new_animal.make_noise}"
puts '----'
end
e il risultato è:
Dog with name Spot becomes Cat
and makes a noise: Meow
----
Cat with name Tom becomes Cat
and makes a noise: Meow
----
-
Un polimorfismo potrebbe essere utile per evitare l'istruzione
if
( antiifcampaign.com ) -
Se si utilizza
RubyOnRails
diventa
il metodo è già implementato:diventa
-
Quicktip: se mescoli il polimorfismo con STI ti porta la combinazione più efficiente per il refactoring del tuo codice
Vorrei che ti aiutasse