【问题标题】:How do you do polymorphism in Ruby?如何在 Ruby 中实现多态性?
【发布时间】:2008-09-26 03:47:45
【问题描述】:

在 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 等鸭子类型语言中的多态性/虚拟方法?

【问题讨论】:

标签: c# ruby polymorphism


【解决方案1】:

到目前为止,所有答案对我来说都很好。我想我只是提到整个继承的事情并不是完全必要的。暂时排除“睡眠”行为,我们可以使用鸭子类型来实现整个期望的结果,并且根本不需要创建一个 Animal 基类。谷歌搜索“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

你知道其余的......

【讨论】:

  • +1 用于混合通用功能而不是继承它。虽然这两种方法都有效,但这更像是“Ruby 的做法”(对于这个特定的例子)。
  • 有趣的是,在CRuby中,这几乎等同于继承:包括模块animal将Dog的超类设置为animal,并且将之前的Dog超类设置为Animal的超类。虽然这是可行的,并且可能是“使用 ruby​​ 的方式”,但应该注意这比继承效率低得多:CRuby 为每个包含它的类制作一个模块的副本(出于上述原因)。跨度>
【解决方案2】:

编辑:为您更新的问题添加了更多代码

免责声明:我一年左右没有使用 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
}

【讨论】:

  • 抱歉,什么版本的 Ruby 是“class Cat : Animal”?不是“
  • 布伦特:那将是“Python 用户记忆中的 Ruby”
  • 那么,class.name 会输出 Animal 还是 Dog?我真的很好奇。
  • 是的,哈哈,我有时会做“Ruby 用户记忆中的 Python” Pythonic 的反面是什么?鲁本斯克?
【解决方案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

【讨论】:

  • 我不是 Ruby 大师,但puts 不输出字符串吗?如果是这样,这个例子在调用sleep 时调用它两次——一次在方法内,一次用它的返回值(不管那是什么!)
  • 你是对的,puts 返回nil,所以外部puts 将简单地print 换行,相当于print("#{nil}\n"),与print("\n") 相同。我没有注意到输出中的尾随换行符,对此感到抱歉。
  • 应该是puts animal.make_noise; animal.sleep,而不是puts animal.sleep
【解决方案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”)

【讨论】:

  • MakeNoise 而不是make_noise?使用returns?为什么不遵循 Ruby 约定?
  • 我为什么要遵循我非常不喜欢的约定?恕我直言,下划线很丑陋,会阻碍阅读流程; CamelCase 更容易阅读。你真的只是因为这个而投票给我吗?嗯是我的答案
  • @Brent- 如果您正在编写其他人使用的代码,您应该遵循约定(不管您的看法如何)。他们会期望 CamelCase 名称指代类,下划线分隔名称指代类方法,并且对您对约定的想法一无所知。如果您只是编写代码供自己阅读,那是一回事,但如果其他人要使用您的代码,请遵循最少惊讶原则。
  • @bta - 我知道你来自哪里。我真的在说两件事:首先,我真的不认为偏离“接受”的约定 here 值得一票否决。其次,“公认”的约定是丑陋的,迫使我在“在社区中”和“代码美”之间做出选择(在 this 旁观者的眼中。是的,我使用回报,因为我认为它使代码更清晰。是的,我使用 CamelCase,因为我认为它更易于阅读。是的,我在这里使用它们是因为我想展示我认为更好的内容,并让其他人判断。
  • @Brent- 我也经常使用return :) 我不同意你对风格的一些看法,但我同意这里的风格不值得一票否决。 +1 为一个可行的解决方案,尽管编码风格。
【解决方案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
      

      真的与其他解决方案没有什么不同,但这是我更喜欢的风格。

      与原始 C# 示例中的 41 行相比,这是 12 行(实际上,您可以使用集合初始化程序减少 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)

        • 如果你使用RubyOnRailsbecomes方法已经实现:becomes

        • 快速提示:如果您将多态性与 STI 混合使用,它将为您带来重构代码的最有效组合

        希望对你有帮助

        【讨论】:

          猜你喜欢
          • 2014-12-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多