Абстрактный метод в Ruby

Как заставить подкласс реализовать метод в Ruby. В Ruby не существует абстрактного ключевого слова, которое я бы использовал в Java. Есть ли еще один Ruby-подобный способ применения абстрактных?

Ответ 1

Абстрактные методы, как предполагается, менее полезны в Ruby, потому что они не сильно статически напечатаны.

Однако это то, что я делаю:

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.

Однако это редко кажется необходимым.

Ответ 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 OK:
Dog.new.bark #=> "Bow wow!"

Обратите внимание, что поскольку классы Ruby не являются статическими, но всегда открыты для изменений, сам класс Dog не может обеспечить существование методов #bark, поскольку он не знает, когда он должен быть завершен. Если вы, как программист, делаете это, вам решать проверить его в такое время.

Ответ 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

Некоторые люди будут жаловаться, что это меньше DRY, и настаивают на создании подкласса исключений и/или помещают строку "SubclassResponsibility" в константу, но IMHO вы можете сушить вещи до такой степени, что их изнасиловали, и это обычно нехорошо. Например. если у вас есть несколько абстрактных классов по базе кода, где бы вы определили константу строки MESS?!?

Ответ 4

Мне нравится использование драгоценного камня, такого как abstract_method, который дает абстрактные методы синтаксиса стиля рельсов dsl:

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 AnstractClass
  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