web-dev-qa-db-fra.com

Méthode abstraite en rubis

Comment puis-je forcer une sous-classe à implémenter une méthode dans Ruby. Il ne semble pas y avoir de mot-clé abstrait en Ruby, ce qui serait l'approche que je prendrais en Java. Existe-t-il une autre manière plus ruby ​​d'appliquer le résumé? 

42
Hunter McMillen

Fortement typé statiquement. cependant, voici ce que je fais: extrait de code.

Cela semble rarement nécessaire, cependant.

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.

45
Andy

J'aime la réponse de pvandenberk, mais je l’améliorerais comme suit:

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

Maintenant, si vous créez une classe appartenant à Canine "classe abstraite" (c'est-à-dire une classe qui a le module Canine dans ses ancêtres), il se plaindra s'il est constaté que la méthode #bark n'est pas implémentée:

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

Notez que, puisque les classes Ruby ne sont pas statiques, mais qu'elles sont toujours ouvertes aux modifications, la classe Dog ne peut elle-même imposer l'existence de méthodes #bark, car elle ne sait pas quand est censée être terminée. Si vous le faites en tant que programmeur, c'est à vous de le tester à ce moment.

15
Boris Stitnicky

Mon approche préférée est similaire mais légèrement différente ... Je la préfère comme suit, car elle rend le code auto-documenté, vous donnant quelque chose de très similaire à 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

Certaines personnes se plaindront que cela est moins sec, et insisteront pour créer une sous-classe d’exceptions et/ou pour mettre la chaîne "SubclassResponsibility" dans une constante, mais IMHO vous pouvez faire sécher les choses jusqu’au point d’être irrité, et c’est généralement bonne chose . Par exemple. Si vous avez plusieurs classes abstraites dans votre base de code, où définiriez-vous la constante de chaîne MESS?!?

7
pvandenberk

J'aime l’utilisation d’une gemme telle que abstract_method qui donne une méthode abstraite à la syntaxe de style Rails:

class AbstractClass
  abstract_method :foo
end

class AbstractModule
  abstract_method :bar
end

class ConcreteClass < AbstractClass
  def foo
    42
  end
end
4
Rob Dawson

Ce code ne vous laissera pas charger la classe si les méthodes 'foo', 'bar' et 'mate' ne sont pas définies dans la classe héritée. 

Cela ne tient pas compte du fait que les classes sont définies dans de nombreux fichiers, mais soyons honnêtes: est-ce que beaucoup d’entre nous définissons réellement les méthodes de classe dans plusieurs fichiers? Je veux dire si vous ne comptez pas les mix-ins. (ce que cela représente)

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
1
baash05

Si vous souhaitez qu'une erreur soit générée lorsque vous créez une instance de la classe, vous pouvez procéder comme suit: 

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
0
Samuel Garratt