Alternatives to abstract classes in Ruby?

2020-08-21 06:02发布

问题:

I am new to Ruby. A simple example, what I need:

class Animal
   abstract eat()

class Cat < Animal
   eat():
     implementation

class Dog < Animal
   eat():
     implementation

In other words, the eat() method should be required for all the classes which extend Animal.

In JAVA I would just use an abstract class, but after doing some research I found that many people don't use it in Ruby and mixin / modules are recommended instead.

However, I don't understand, if modules can do more than just include an addition methods. To be exact, can modules set the requirements for classes which methods they must implement (if yes, an example would be appreciated)?

To sum up, what should I use in this case, when I want to be sure, that all classes of the same type have particular methods and implement them in their own way?

回答1:

Ruby does not offer this functionality, no. You are responsible for making sure that your classes implement what they ought to implement.

Part of the reason that such functionality is impossible for Ruby is that Ruby classes can be reopened, and Ruby supports loading arbitrary code at runtime, so we can't know whether a class implements a certain interface until we try to call it.

Supposing an Animal must have an eat method, and I do the following:

class Cat < Animal
  def talk
    puts "meow"
  end
end

class Cat
  def eat
    puts "om nom nom"
  end
end

By the end of that file, the Cat will have its eat definition, because Ruby classes can reopened and modified multiple times. Should the code error out after the first definition because eat wasn't defined yet? That implementation would hurt more than it would help, since reopening classes is common, even if this example is contrived. Should it error out once the eat method is called and does not exist, so we can be certain that it's defined once we need it? Well, if the method were missing, that would happen, anyway. The interpreter can never know if another class definition is on the way, so it can never cut you off until the method is actually called.

In short, superclasses simply cannot possibly require a method to be defined in Ruby, because the dynamic nature of classes contradict such a goal.

Sorry! This is a place where unit testing might come in handy, though, to ensure that your subclasses do what they're supposed to be doing, anyway.



回答2:

Use a module which defines the methods that must be implemented.

module Animal
  def eat
    raise NotImplementedError
  end
end

class Cat
  include Animal

  def eat
    "Om nom nom"
  end
end

class Dog
  include Animal
end

c = Cat.new
c.eat # => "Om nom nom"

d = Dog.new
d.eat # => NotImplementedError


回答3:

Yo can just use empty methods, or make them raise errors to force subclasses to implement them.

class Base
  def abstract_method
  end

  def mandatory_abstract_method
    raise NotImplementedError.new("You must implement this")
  end
end

The down side is this will only enforce it when the method is actually called. Ruby is a dynamic language and it doesn't have any compile-time checking.



回答4:

There's no equivalent to abstract classes in Ruby. Mostly this is because Ruby is dynamically typed, which means that the concept of abstract classes doesn't really apply.



回答5:

If you really want to implement abstraction then you use abstraction gem.

sudo gem install abstraction

Referencing example Example from Abstraction gem WIKI.

class Car
  abstract

  def go_forward
  # ...
 end
end

Car.new
> AbstractClassError: Car is an abstract class and cannot be instantiated

class Convertible < Car
  def door_count
    2
  end
end

class Sedan < Car
  def door_count
    4
  end
end

Convertible.new  # => #<Convertible:0x8fdf4>