ruby-on-rails Ruby/Rails -强制子类重写特定的方法?

djp7away  于 2022-12-15  发布在  Ruby
关注(0)|答案(3)|浏览(141)

我想知道在Ruby或Rails中是否有一种方法可以强制子类覆盖父方法中的方法(在Java中,您可以使用abstract class/method来实现这一点)。
假设我有以下几个类:

class Pet
end

class Dog < Pet
  def collar_color
    "red"
  end
end

class Cat < Pet
end

dog = Dog.new
dog.collar_color
==> red

cat = Cat.new
cat.collar_color
==> NoMethodError

在这个例子中,我永远不会示例化一个Pet对象,但是它的存在是为了作为一种收集公共类的公共方法的方式。但是让我们假设我想确保Pet的所有子类都覆盖collar_color方法。有办法做到吗?我可以通过某种方式测试来实现吗?假设我不想在父类中定义一个默认值。
我的实际用例是一个类的集合,所有类都拥有另一个类的多态所有权,如果我有一个被拥有类的显示页面,那么其中一个没有方法的所有者类可能会给我留下一个NoMethodError问题。

gudnpqoy

gudnpqoy1#

不,没有办法强制执行。
我可以向你保证,不管你能想出什么主意,它都会以某种方式破灭。
首先:静态地做这件事是不可能的。确定一个方法是否被覆盖已经知道等同于解决停止问题。
所以,你必须动态地做,但即使这样也会有问题。
例如:你可以实现inherited钩子,并检查是否每个从Pet继承的类都实现了这个方法。但是,这会阻止某些人继承他们自己的抽象类。(同样,也不能保证inherited钩子何时运行--它可能在类打开时运行,也就是在定义方法之前。)
另外,即使你可以检查这个方法是否存在于类继承Pet的地方,这个方法仍然可以在以后被删除,所以你不能得到任何保证。当然,他们可以提供一个伪方法,以绕过你的保护。
您 * 可以 * 创建方法的默认实现,这些方法只是raiseException,但没有必要这样做:如果您 * 不 * 创建默认实现,那么无论如何raise已经是一个NoMethodError异常(如果您 * 这样做 不要 * 使用NotImplementedError,而是使用从RuntimeError继承自定义异常)。
核心库中有这样的示例:例如,Enumerable mixin依赖于一个抽象方法each,而这个抽象方法必须由子类来实现,处理的方法就是简单地记录这个事实:

用法

要在集合类中使用模块Enumerable:

  • 包含它:
include Enumerable
  • 实现必须生成集合的连续元素的方法#each。几乎所有可枚举方法都将调用该方法。

这实际上是Ruby从一开始就处理任何类型相关问题的方式,因为Ruby没有类型,类型只发生在程序员的头脑中,类型信息只写在文档中。
一直以来都有各种IDE使用的非正式的第三方类型注解语言。最近,引入了两种类型注解语言:RBI,一种由Sorbet类型检查器使用的第三方类型注解语言,以及RBS,一种属于Ruby本身的类型注解语言。
据我所知,RBS没有表示抽象方法的方法,但是RBI does

class Pet
  extend T::Sig
  extend T::Helpers
  interface!

  sig {abstract.returns(String)}
  def collar_color; end
end

如果继承链中的某个子类示例化了一个对象,而这个子类在某个点上没有定义这个方法,那么这会给予你一个类型错误,但是,当然,* 只有 * 代码的用户使用Sorbet这样的类型检查器对代码进行了类型检查,如果代码的用户没有对它进行类型检查,他们不会得到类型错误。

z9gpfhce

z9gpfhce2#

Ruby的关键字相对较少,但它提供了基本的构建块来实现类似于抽象类或方法的东西。
最简单的形式是在父“abstract”方法中引发一个错误:

class AbstractMethodError < StandardError
  def initialize(klass, m)
    super("Expected #{klass} to implement #{m}")
  end
end

class Pet
  def collar_color
    raise AbstractMethodError.new(self.class, __method__)
  end
end

class Cat < Pet
  
end

Cat.new.collar_color # Expected Cat to implement collar_color (AbstractMethodError)

__method__是一个包含当前方法名称的魔术变量。
你可以通过创建一个定义“抽象方法”的类方法来使它更优雅一些:

module Abstractions
  def abstract_method(name)
    define_method(name) do
      raise AbstractMethodError.new(self.class, __method__)
    end
  end
end

class Pet
  extend Abstractions
  abstract_method :collar_color
end

然而Ruby是一种动态语言,你没有编译时检查,所以这只会在调用方法时给予一个稍微明显的错误信息,它实际上并没有给出子类实现方法的任何保证。
这取决于测试或使用像冰沙或RBS这样的类型检查器。一般来说,当你学习忘记你认为你所知道的关于面向对象编程的一切,学习Ruby的方式时,它可能会很有帮助。与Java相比,它有一个非常不同的设计哲学--你使用鸭子类型来查看对象是否响应该方法,而不是抽象的方法和接口。

xqk2d5yq

xqk2d5yq3#

只需通过引发Not implemented error或其他错误在抽象类中定义默认方法实现。通过这样做,您还可以在类设计中澄清,当其他人/您想要继承Pet类时,他们需要覆盖collar_color方法。澄清是一个很好的想法,不在抽象类中定义默认方法没有任何好处。
或者如果你想通过测试来实现,你可以为Pet类创建一个测试用例,检查它的后代是否定义了自己的collar_color方法。我认为Rails / Ruby 3.1定义了.descendants方法,或者你可以谷歌一下。

# Pet_spec.rb 
describe "descendants must implement collar_color" do
  it "should not throw error" do
    descendants = Pet.descendants
    descendants.each do |descendant|
      expect { descendant.new.collar_color }.to.not raise_error
    end
  end
end

相关问题