Как вы выполняете полиморфизм в 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!";
}
}
Очевидно, выход (слегка перефразированный):
- Гав
- Собака спала
- Мяу
- Кошка спит
Так как С# часто высмеивается за его подробный синтаксис типа, как вы обрабатываете полиморфизм/виртуальные методы на утином языке, например, Ruby?
Ответы
Ответ 1
edit: добавлен код для обновленного вопроса
отказ от ответственности: я не использовал Ruby через год или около того и не устанавливал его на этом компьютере, поэтому синтаксис может быть совершенно неправильным. Но понятия верны.
Точно так же, с классами и переопределенными методами:
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
}
Ответ 2
Все ответы до сих пор выглядят неплохо для меня. Я думал, что я просто упомянул, что все наследство не совсем необходимо. Исключая поведение "сна" на мгновение, мы можем достичь всего желаемого результата с помощью утиного ввода и опустить необходимость создания базового класса Animal. Googling для "duck-typing" должен давать любое количество объяснений, поэтому здесь можно просто сказать "если он ходит как утка и шарлатанцы, как утка..."
Поведение "сна" может быть обеспечено с помощью модуля mixin, такого как Array, Hash и другие встроенные классы Ruby, включая Enumerable. Я не предлагаю это обязательно лучше, просто другой и, возможно, более идиоматично Ruby способ сделать это.
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
Вы знаете все остальное...
Ответ 3
Использование идиоматического 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
Ответ 4
Основываясь на предыдущем ответе, это как вы можете это сделать?
Второй разрез после уточнения:
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
}
(Я оставлю это как есть, но "self.class.name" побеждает ".to_s" )
Ответ 5
Принцип утиной печати заключается в том, что объект должен отвечать на вызванные методы. Так что что-то подобное может сделать и трюк:
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
Ответ 6
Небольшой вариант решения manveru, который динамически создает другой тип объекта, основанный на массиве типов классов. Не совсем по-другому, немного понятнее.
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
Ответ 7
Вот как я его напишу:
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
Это не очень отличается от других решений, но это тот стиль, который я бы предпочел.
Это 12 строк по сравнению с 41 строкой (на самом деле вы можете сбрить 3 строки с помощью инициализатора коллекции) из исходного примера С#. Неплохо!
Ответ 8
Существует метод becomes
, который реализует полиморфизм (путем копирования всех переменных экземпляра из заданного класса в новый)
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
и результат:
Dog with name Spot becomes Cat
and makes a noise: Meow
----
Cat with name Tom becomes Cat
and makes a noise: Meow
----
-
Полиморфизм может быть полезен, чтобы избежать утверждения if
(antiifcampaign.com)
-
Если вы используете RubyOnRails
becomes
, метод уже реализован: becomes
-
Quicktip: если вы смешиваете полиморфизм с STI, он приносит вам наиболее эффективную комбинацию для рефакторинга кода
Я бы хотел, чтобы это помогло вам