Как вы выполняете полиморфизм в Ruby?
-
02-07-2019 - |
Вопрос
В C # я могу сделать это:
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!";
}
}
Очевидно, что результат (Слегка перефразированный):
- Гав
- Собака спит
- Мяу
- Кошка спит
Поскольку C # часто высмеивают за его подробный синтаксис типов, как вы справляетесь с полиморфизмом / виртуальными методами на языке с утиной типизацией, таком как Ruby?
Решение
Редактировать:добавлено больше кода для вашего обновленного вопроса
Отказ от ответственности:Я не пользовался 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
}
Другие советы
Все ответы на данный момент кажутся мне довольно хорошими.Я подумал, что стоит просто упомянуть, что вся эта история с наследованием не совсем обязательна.Исключив на мгновение поведение "sleep", мы можем достичь всего желаемого результата, используя утиный ввод и вообще не создавая базовый класс Animal.Поиск в Google "утиного набора текста" должен дать любое количество объяснений, поэтому здесь давайте просто скажем: "если он ходит как утка и крякает как утка ..."
Поведение "спящего режима" может быть обеспечено с помощью модуля 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
Остальное вы знаете...
Использование идиоматического 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
Основываясь на предыдущем ответе, вот как вы могли бы это сделать?
Второй срез после осветления:
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")
Принцип утиного ввода заключается просто в том, что объект должен реагировать на вызываемые методы.Так что что-то подобное тоже может сработать :
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
Небольшой вариант решения 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
Вот как я бы это написал:
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 строки, используя инициализатор коллекции) из исходного примера C #.Неплохо!
Есть такой метод 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
Быстрая подсказка:если вы смешиваете полиморфизм с СТИ это дает вам наиболее эффективную комбинацию для рефакторинга вашего кода
Я бы хотел, чтобы это помогло тебе