开发者

Abstract Method in Ruby

开发者 https://www.devze.com 2023-03-22 12:08 出处:网络
How can I force a subclass to implement a method in Ruby. There doesn\'t seem to be an abstract keyword in Ruby, which is the approach I would take in Java. Is there another more Ruby-like way to e开发

How can I force a subclass to implement a method in Ruby. There doesn't seem to be an abstract keyword in Ruby, which is the approach I would take in Java. Is there another more Ruby-like way to e开发者_开发问答nforce abstract?


Abstract methods are supposed to be less useful in Ruby because it's not strongly statically typed.

However, this is what I do:

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.

It rarely seems to be necessary, however.


I like the answer by pvandenberk, but I would improve it as follows:

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

Now if you make a class belonging to Canine "abstract class" (ie. a class that has Canine module in its ancestors), it will complain if it is found that #bark method is not implemented:

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!"

Note that since Ruby classes are not static, but always open to changes, Dog class itself cannot enforce existence of #bark methods, since it doesn't know when is it supposed to be finished. If you as a programmer do, it is up to you to test it at such time.


My preferred approach is similar but slightly different... I prefer it as follows, because it makes the code self-documenting, giving you something very similar to 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

Some people will complain that this is less DRY, and insist on creating an exception subclass and/or put the "SubclassResponsibility" string in a constant, but IMHO you can dry things up to the point of being chafed, and that is not usually a good thing. E.g. if you have multiple abstract classes across your code base, where would you define the MESS string constant?!?


I like the use of a gem like abstract_method which gives a dsl rails style syntax abstract methods:

class AbstractClass
  abstract_method :foo
end

class AbstractModule
  abstract_method :bar
end

class ConcreteClass < AbstractClass
  def foo
    42
  end
end


This code will not let you load the class if the methods 'foo', 'bar' and 'mate' are not defined in the inherited class.

It does not account for classes being defined across many files, but lets get honest do many of us actually define class methods across many files? I mean if you don't count mix-ins. (which this does account for)

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


If you want to have an error thrown when you create an instance of the class you could do the following

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


Because the question is (focus on) "How can I force a subclass to implement a method in Ruby", so i think we can use TDD :D, for example: 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

Maybe Tests are better than Abstract :D , reference: http://morningcoffee.io/interfaces-in-ruby.html

0

精彩评论

暂无评论...
验证码 换一张
取 消