2008-09-26 18 views
14

En C#, je peux le faire:Comment faites-vous le polymorphisme dans Ruby?

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!"; 
    } 
} 

De toute évidence, la sortie est (légèrement paraphrasé):

  • Woof
  • chien dort
  • Meow
  • Cat Sleeping

Comme on se moque souvent de C# pour sa syntaxe de type verbose, comment gérez-vous les polymorphismes/méthodes virtuelles dans un langage de type canard tel que Ruby?

+0

Vous avez mentionné le "vrai" polymorphisme. Quelle est votre définition de «réel» Je vous laisse le mien: http://en.wikipedia.org/wiki/Polymorphism_in_object-oriented_programming – OscarRyz

Répondre

15

modifier: a ajouté plus de code pour votre question mise à jour

Avertissement: Je ne l'ai pas utilisé Ruby dans un an, et ne l'ont pas installé sur cette machine, la syntaxe peut-être tout à fait tort. Mais les concepts sont corrects.


La façon exacte même, avec des classes et des méthodes surchargées:

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 
} 
+0

Désolé, mais quelle version de Ruby est "class Cat: Animal"? N'est-ce pas "<" pour l'héritage? –

+4

Brent: ce serait "Ruby comme le rappelle un utilisateur Python" –

+1

Alors, class.name sort Animal ou Dog? Je suis vraiment curieux. – FlySwat

2

Miser sur la réponse précédente, est-ce que vous pourriez le faire?


deuxième coupe après clarification:

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 
} 

(je vais laisser comme tel, mais "self.class.name" l'emporte sur ".to_s")

+1

'MakeNoise' par opposition à' make_noise'? En utilisant 'retours'? Pourquoi ne pas suivre les conventions de Ruby? –

+0

Pourquoi devrais-je suivre une convention que je n'aime pas intensément? À mon humble avis, soulignent est laid et entrave le flux de la lecture; CamelCase est beaucoup plus facile à lire. Avez-vous vraiment voté pour moi juste pour ça? Meh est ma réponse –

+1

@ Brent- Vous devriez suivre une convention (indépendamment de votre opinion) si vous écrivez du code que d'autres personnes utilisent. Ils s'attendront à ce que les noms CamelCase se réfèrent aux classes et aux noms underscore_separated pour se référer aux méthodes de classe et ne sauront rien de vos pensées sur la convention. Si vous écrivez seulement du code pour vous-même, alors c'est une chose mais si d'autres vont utiliser votre code, allez avec le principe de la plus petite surprise. – bta

10

En utilisant idiomatiques Ruby

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 
+0

Je ne suis pas un gourou Ruby, mais ne met pas en sortie la chaîne? Si oui, cet exemple l'appelle deux fois en appelant 'sleep' - une fois dans la méthode, et une fois avec sa valeur de retour (quelle qu'elle soit!) –

+0

Vous avez raison, et' puts' renvoie 'nil', donc le outer 'puts' ne ferait qu'imprimer un retour à la ligne, équivalent à' print ("# {nil} \ n") ', qui est identique à' print ("\ n") '. Je n'ai pas remarqué le retour à la ligne dans ma sortie, désolé à ce sujet. – manveru

+0

Cela devrait être 'puts animal.make_noise; animal.sleep', pas 'puts animal.sleep' – YoTengoUnLCD

0

Voilà comment je l'écrirais:

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 

Ce n'est pas vraiment différent des autres solutions, mais c'est le style que je préférerais.

C'est 12 lignes par rapport aux 41 lignes (en fait, vous pouvez raser 3 lignes en utilisant un initialiseur de collection) de l'exemple C# original. Pas mal!

22

Toutes les réponses à ce jour me semblent très bonnes. Je pensais juste mentionner que tout l'héritage n'est pas entièrement nécessaire. En excluant le comportement de «sommeil» pendant un moment, nous pouvons atteindre le résultat souhaité en utilisant le typage du canard et en omettant le besoin de créer une classe de base animale du tout. Google pour "dactylographie" devrait donner un certain nombre d'explications, alors, disons simplement "si ça marche comme un canard et des charlatans comme un canard ..."

Le comportement "sommeil" pourrait être fourni en utilisant un module mixin, comme Array, Hash et d'autres classes intégrées Ruby incluent Enumerable. Je ne dis pas que c'est forcément mieux, juste une manière différente et peut-être plus idiomatique de le faire.

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 

Vous connaissez le reste ...

+2

+1 pour mélanger la fonctionnalité commune au lieu de l'hériter. Bien que les deux méthodes fonctionnent, il s'agit plus de "la façon Ruby de le faire" (pour cet exemple particulier). – bta

+0

C'est marrant que dans CRUBY, cela soit presque équivalent à l'héritage: y compris le module animal place la super-classe de Dog à être animal, et la super-classe précédente de Dog à être la superclasse de Animal. Bien que cela fonctionne, et peut être "la façon rubis de le faire", il faut noter que cela est beaucoup moins efficace que l'héritage: CRuby fait une copie du module pour chaque classe y compris (pour la raison indiquée ci-dessus). – YoTengoUnLCD

1

Le principe du typage de canard est que l'objet doit répondre aux méthodes appelées. Donc, quelque chose comme ça peut faire l'affaire aussi:

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 
1

Une petite variante de la solution de Manveru qui dynamique créer différents types d'objet à partir d'un tableau de types de classe. Pas vraiment différent, juste un peu plus clair.

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 
0

Il y a une méthode becomes qui met en oeuvre un polymorphisme (par adaptation toutes les variables d'instance de la classe donnée à nouveau)

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 

et le résultat est:

Dog with name Spot becomes Cat 
and makes a noise: Meow 
---- 
Cat with name Tom becomes Cat 
and makes a noise: Meow 
---- 
  • A le polymorphisme pourrait être utile pour éviter la déclaration if (antiifcampaign.com)

  • Si vous utilisez RubyOnRails méthode becomes est déjà mis en œuvre: becomes

  • Quicktip: si vous polymorphisme mélanger avec STI il vous apporte le combo le plus efficace pour factoriser votre code

I Je souhaite qu'il vous a aidé