Ruby 中的抽象方法

Posted

技术标签:

【中文标题】Ruby 中的抽象方法【英文标题】:Abstract Method in Ruby 【发布时间】:2011-10-11 04:03:11 【问题描述】:

如何强制子类在 Ruby 中实现方法。 Ruby 中似乎没有抽象关键字,这是我在 Java 中采用的方法。还有另一种更类似于 Ruby 的方式来执行抽象吗?

【问题讨论】:

此外,Ruby 使用了一种叫做 Duck Typing 的东西:en.wikipedia.org/wiki/Duck_typing @delnan,没有必要这样说你的答案。如果我试图坚持 Java 思维方式,我就不会要求“类似 Ruby”的解决方案。但是,谢谢您对运行时异常的建议。 如果我失礼了,我很抱歉。我刚刚看到 这么多 人试图用语言 A 编程,就好像它是语言 B。你的问题似乎也有点像这样,因为你问如何做抽象类在 Java 中所做的事情(而不是“相当于抽象类的红宝石”或类似 zhsz 的东西)。再说一次,没有冒犯的意思,也许我弄错了。 【参考方案1】:

抽象方法在 Ruby 中的用处应该不大,因为它不是strongly静态类型的。

但是,我就是这样做的:

class AbstractThing
  MESS = "SYSTEM ERROR: method missing"

  def method_one; raise MESS; end
  def method_two; raise MESS; end
end

class ConcreteThing < AbstractThing
  def method_one
     puts "hi"
  end
end

a = ConcreteThing.new
a.method_two # -> raises error.

然而,它似乎很少需要。

【讨论】:

Ruby 是强类型的。只是不是静态类型的。 +1,这就是在 Smalltalk 中使用subclassResponsibility (^ self subclassResponsibility) 的方式。 如果您只是删除AbstractThing 的全部内容,您会得到完全相同的行为:尝试调用method_two 时出现异常。事实上,你得到了一个稍微更好的行为,因为你得到一个RuntimeError,而不是一个通用的非描述性、非语义的NoMethodError,它可以准确地告诉你你的代码出了什么问题. @Jorg。这大体上是正确的,当然除了两件事——首先,我可以提出我喜欢的任何错误;我只是保持这个例子简单。关键是你得到一个更具体的错误。其次,定义抽象类让任何阅读代码的人都清楚你的意图(特别是如果你多次子类化它,通常是这种情况)。 这不一样。如果一个方法是抽象的,你应该在加载类时得到一个错误,甚至不需要显式调用丢失的方法。【参考方案2】:

我喜欢 pvandenberk 的回答,但我会改进如下:

module Canine      # in Ruby, abstract classes are known as modules
  def bark
    fail NotImplementedError, "A canine class must be able to #bark!"
  end
end

现在如果你创建一个属于Canine“抽象类”的类(即一个在其祖先中有Canine模块的类),如果发现#bark方法没有实现,它会报错:

class Dog
  include Canine   # make dog belong to Canine "abstract class"
end

Dog.new.bark       # complains about #bark not being implemented

class Dog
  def bark; "Bow wow!" end
end

# Now it's OK:
Dog.new.bark #=> "Bow wow!"

请注意,由于 Ruby 类不是静态的,而是始终对变化开放,Dog 类本身不能强制存在#bark 方法,因为它不知道应该何时完成。如果您作为程序员这样做,那么您可以在这个时候对其进行测试。

【讨论】:

我在搜索 NotImplementedError 时遇到了 this。 NotImplementedError 的目的是表明当前平台上不可用的方法(例如,在只有基于 nix 的系统具有的 Windows 上调用 API)。 ruby-doc.org/core-2.5.0/NotImplementedError.html【参考方案3】:

我的首选方法类似但略有不同...我更喜欢它如下,因为它使代码自我记录,为您提供与 Smalltalk 非常相似的东西:

class AbstractThing
  def method_one; raise "SubclassResponsibility" ; end
  def method_two; raise "SubclassResponsibility" ; end
  def non_abstract_method; method_one || method_two ; end
end

有些人会抱怨这不太干燥,并坚持创建一个异常子类和/或将"SubclassResponsibility" 字符串放在一个常量中,但恕我直言you can dry things up to the point of being chafed, and that is not usually a good thing。例如。如果您的代码库中有多个抽象类,您将在哪里定义 MESS 字符串常量?!?

【讨论】:

用符号击败 em 怎么样 :) 那么它是一个浮动的常量,而不是每种情况下的字符串。喜欢你的回答。 唯一反对的是,子类也必须实现no_abstract_method,否则调用时(大概是为了测试)会调用方法一,可能会调用方法二。【参考方案4】:

我喜欢使用像 abstract_method 这样的 gem,它提供了 dsl rails 样式的语法抽象方法:

class AbstractClass
  abstract_method :foo
end

class AbstractModule
  abstract_method :bar
end

class ConcreteClass < AbstractClass
  def foo
    42
  end
end

【讨论】:

【参考方案5】:

如果在继承的类中没有定义方法 'foo'、'bar' 和 'mate',此代码将不允许您加载该类。

它不考虑在许多文件中定义的类,但老实说,我们中的许多人实际上是在许多文件中定义类方法吗?我的意思是,如果你不计算混音的话。 (这确实说明了)

def self.abstract(*methods_array)
  @@must_abstract ||= []
  @@must_abstract = Array(methods_array)
end
def self.inherited(child)
   trace = TracePoint.new(:end) do |tp|
      if tp.self == child #modules also trace end we only care about the class end   
        trace.disable
        missing = ( Array(@@must_abstract) - child.instance_methods(false) )
        raise NotImplementedError, "#child must implement the following method(s) #missing" if missing.present?
      end
  end 
  trace.enable
end

abstract :foo
abstract :bar, :mate

【讨论】:

【参考方案6】:

如果你想在创建类的实例时抛出错误,你可以执行以下操作

class AbstractClass
  def self.new(args)
    instance = allocate # make memory space for a new object
    instance.send(:default_initialize, args)
    instance.send(:initialize, args)
    instance
  end

  #This is called whenever object created, regardless of whether 'initialize' is overridden
  def default_initialize(args)
    self.abstract_method #This will raise error upon object creation
  end
  private :default_initialize

  def initialize(args)
   # This can be overridden by new class
  end
end


class NewClass < AbstractClass
end

NewClass.new #Throw error

【讨论】:

【参考方案7】:

因为问题是(专注于)“如何强制子类在 Ruby 中实现方法”,所以我认为我们可以使用 TDD :D,例如:rspec shared example

shared_examples "MUST implement abstract method" do |method_sym|
  it  is_expected.to respond_to(method_sym) 
end

describe Stack do
  it_behaves_like "MUST implement abstract method", :push
  it_behaves_like "MUST implement abstract method", :pop
end

也许Tests are better than Abstract :D ,参考:http://morningcoffee.io/interfaces-in-ruby.html

【讨论】:

以上是关于Ruby 中的抽象方法的主要内容,如果未能解决你的问题,请参考以下文章

C#中的抽象类抽象方法和虚方法

Java中抽象类和抽象方法的区别

PHP 抽象类,接口,抽象方法,静态方法

Java中的抽象类

php中的抽象类和抽象方法

php中的抽象方法和抽象类