12
votes

Déléguer les méthodes d'instance à la méthode de la classe

Dans Ruby, supposons que j'ai une classe foo pour me permettre de cataloguer ma grande collection de foos. C'est une loi fondamentale de la nature que tous les foos sont verts et sphériques, j'ai donc défini des méthodes de classe comme suit: xxx

Cela me permet de faire xxx

mais pas xxx

malgré le fait que my_foo est clairement vert.

Évidemment, je pourrais définir une méthode d'instance couleur qui appelle self.class.colour , mais qui devient manifeste si j'ai de nombreuses caractéristiques fondamentales.

Je peux également le faire en définissant méthodal_missing pour essayer la classe pour toute méthode manquante, mais je ne suis pas clair si c'est quelque chose que je devrais faire ou un piratage laids, Ou comment le faire en toute sécurité (surtout que je suis en fait sous Activerecord dans des rails, que je crois comprendre fait des trucs amusants intelligents avec la méthode_missing).

Que recommanderiez-vous?


4 commentaires

Sans vouloir débattre de la Colo (U) R du bikéché, il peut être judicieux d'utiliser l'anglais américain en code plutôt que le Commonwealth English.


C'est mon propre code personnel; Les seules personnes devraient toujours voir que c'est moi et quelques amis aussi de la Grande-Bretagne. Je vais utiliser un code américain en milieu de travail si le style house le confirme - je ne suis pas sur le point de l'utiliser dans la mienne.


@Chowlett, c'est certainement votre choix. Mais vous les mélangerez toujours, par ex. Lorsque vous accédez à votre @Colour dans une méthode Initialiser .


En outre, je voudrais inverser et déléguer des méthodes de classe dans des méthodes d'interdiction, si vous n'avez pas besoin de l'état en premier lieu, vous devez utiliser une construction apatride.


8 Réponses :


4
votes

Vous pouvez utiliser un module:

module FooProperties
  def colour ; "green" ; end
  def is_spherical? ; true ; end
end

class Foo
  extend FooProperties
  include FooProperties
end


1 commentaires

Agréable. Cela peut-il gérer l'héritage? C'est-à-dire que si la chose et la barre héritent de FOO, et les choses sont toujours "lourdes", tandis que les barres sont toujours "légères"; Aurais-je besoin de munitions séparées et de modules de barproperties, ou je peux le rouler en bœufroperties en quelque sorte?



1
votes

Cela va sonner comme un peu de flic, mais dans la pratique, il est rarement nécessaire de le faire, lorsque vous pouvez appeler FOO.Color tout aussi facilement. L'exception est si vous avez de nombreuses classes avec des méthodes de couleur définies. @Var pourrait être l'une des classes de plusieurs classes et vous souhaitez afficher la couleur, peu importe.

Quand c'est le cas, je vous demanderais d'où vous utilisez la méthode davantage - sur la classe ou sur le modèle? C'est presque toujours l'un ou l'autre et il n'y a rien de mal à en faire une méthode d'instance même si elle devrait être la même dans toutes les instances.

Dans l'événement rare, vous souhaitez que la méthode "appelable" par les deux, vous pouvez soit faire @ var.class.color (sans créer une méthode spéciale) ni créer une méthode spéciale comme suit:

de couleur def Self.class.color fin

J'éviterais certainement la solution Catch (Method_missing), car elle vous excuse de vraiment envisager l'utilisation de chaque méthode, et si elle appartient au niveau de la classe ou de l'instance.


0 commentaires

4
votes

Dans une perspective de conception, je dirais que, même si la réponse est la même pour tous les foos , couleur et sphérique? sont des propriétés des instances de foo et comme telles doivent être définies comme des méthodes d'instance plutôt que des méthodes de classe.

Je peux cependant voir certains cas où vous voudriez que ce comportement E.G. Lorsque vous avez barres dans votre système, qui sont également bleus et que vous êtes passé une classe quelque part dans votre code et que vous souhaitez savoir quelle couleur une instance sera avant d'appeler nouveau < / code> sur la classe.

En outre, vous êtes correct que ActiveRecord utilise une utilisation intensive de Method_missing E.G. Pour les résultats dynamiques, si vous êtes tombé en panne de cet itinéraire, vous devez vous assurer que votre méthode_MISSING a appelé celui de la superclasse s'il a déterminé que le nom de la méthode n'était pas celui qu'il pourrait se charger de lui-même.


1 commentaires

Vous avez absolument raison, ce sont des propriétés de l'instance plutôt que de la classe - mais vous avez cloué la situation dont j'ai besoin de cette fonction; Je dois prendre une décision dans mon code lorsque j'ai la classe dans la main, avant d'appeler nouveau.



3
votes

Je pense que la meilleure façon de le faire serait d'utiliser La méthode de la matrice de Dwemthy .

Je vais le chercher et combler les détails, mais voici le squelette p>

edit strong> : Yay! Travailler! P>

class Object
  # class where singleton methods for an object are stored
  def metaclass
    class<<self;self;end
  end
  def metaclass_eval &block
    metaclass.instance_eval &block
  end
end
module Defaults
  def self.included(klass, defaults = [])
    klass.metaclass_eval do
      define_method(:add_default) do |attr_name|
        # first, define getters and setters for the instances
        # i.e <class>.new.<attr_name> and <class>.new.<attr_name>=
        attr_accessor attr_name

        # open the class's class
        metaclass_eval do
          # now define our getter and setters for the class
          # i.e. <class>.<attr_name> and <class>.<attr_name>=
          attr_accessor attr_name
        end

        # add to our list of defaults
        defaults << attr_name
      end
      define_method(:inherited) do |subclass|
        # make sure any defaults added to the child are stored with the child
        # not with the parent
        Defaults.included( subclass, defaults.dup )
        defaults.each do |attr_name|
          # copy the parent's current default values
          subclass.instance_variable_set "@#{attr_name}", self.send(attr_name)
        end
      end
    end
    klass.class_eval do
      # define an initialize method that grabs the defaults from the class to 
      # set up the initial values for those attributes
      define_method(:initialize) do
        defaults.each do |attr_name|
          instance_variable_set "@#{attr_name}", self.class.send(attr_name)
        end
      end
    end
  end
end
class Foo
  include Defaults

  add_default :color
  # you can use the setter
  # (without `self.` it would think `color` was a local variable, 
  # not an instance method)
  self.color = "green"

  add_default :is_spherical
  # or the class instance variable directly
  @is_spherical = true
end

Foo.color #=> "green"
foo1 = Foo.new

Foo.color = "blue"
Foo.color #=> "blue"
foo2 = Foo.new

foo1.color #=> "green"
foo2.color #=> "blue"

class Bar < Foo
  add_defaults :texture
  @texture = "rough"

  # be sure to call the original initialize when overwriting it
  alias :load_defaults :initialize
  def initialize
    load_defaults
    @color = += " (default value)"
  end
end

Bar.color #=> "blue"
Bar.texture #=> "rough"
Bar.new.color #=> "blue (default value)"

Bar.color = "red"
Bar.color #=> "red"
Foo.color #=> "blue"


5 commentaires

Cela pourrait être ce que je recherche ... dans l'attente de la mise à jour détaillée.


@Chris: la mise à jour détaillée est terminée. Faites-moi savoir si vous rencontrez des problèmes avec cela.


Actuellement, la couleur n'est pas héritée (car elle est stockée dans une variable d'instance de la classe), mais vous pouvez la modifier de manière à ce que les valeurs de défaut actuelles soient héritées lorsqu'une classe est sous-classée en écrivant également une autre chose.


Fancy Stuff là-bas ... Chris, si vous souhaitez apprendre plus de métaprogrammation de choses comme celle-ci, je vous recommande des screencasts de Dave Thomas à PragProg.com. Ils ne sont que 5 $ et ça vaut vraiment la peine: PragProg .com / Screencasts / V-DTRubyom / ...


Prete magie ici. Je vais aller avec la solution de passthrough d'Aidan, mais je pense que je voudrais prendre le temps de comprendre cela aussi.



1
votes

Vous pouvez définir une installation passthrough: xxx

Je n'aime généralement pas utiliser eval , mais il devrait être assez sûr, ici.


3 commentaires

C'est aussi un concurrent très fort pour exactement ce dont j'ai besoin.


Celui-ci traitera avec beaucoup mieux la succession que l'autre réponse que j'ai postée.


Ouais. C'est simple, je comprends ce que ça fait, il gère l'héritage, et il arrive beaucoup ressembler à des déclarations de style rails (HAS_MANY, etc.)!



24
votes

Le module transférable avec RUBY le fera bien:

#!/usr/bin/ruby1.8

require 'forwardable'

class Foo

  extend Forwardable

  def self.color
    "green"
  end

  def_delegator self, :color

  def self.is_spherical?
    true
  end

  def_delegator self, :is_spherical?

end

p Foo.color                # "green"
p Foo.is_spherical?        # true
p Foo.new.color            # "green"
p Foo.new.is_spherical?    # true


3 commentaires

Légèrement plus court: déf_delegator Soi,: Couleur


Notez que si vous donnez def_delegator un symbole pour son premier argument, il sera évalué que plus tard. Donc, si vous sous-classe foo avec foochild , et que vous souhaitez que les instances de FOOCHILD soient déléguées à foochild et non foo , utilisation DEF DÉLÉDATEUR: SOFT,: Couleur Pour garder auto d'être évalué sous forme foo . Cela semble vraiment être un eval: def_delegator: "met" hi '; auto " aura le même effet mais imprime à la norme.


En ce qui concerne les commentaires précédents, vous voudriez déléguer à : 'Self.class' comme : auto délégués à l'instance, pas la classe lorsqu'elle est évaluée.



2
votes

Vous pouvez également faire cela:

def self.color your_args; your_expression end

define_method :color, &method(:color)


0 commentaires

20
votes

Si c'est un simple rubis, alors en utilisant transférable a> est la bonne réponse

Si ce serait des rails, j'aurais utilisé délégué , par exemple P>

class Foo
  delegate :colour, to: :class

  def self.colour
    "green"
  end
end

irb(main):012:0> my_foo = Foo.new
=> #<Foo:0x007f9913110d60>
irb(main):013:0> my_foo.colour
=> "green"


1 commentaires

Si vous déléguez au sein d'un ActiveSupport :: Préoccupation Préoccupation, placez le Délégué appel à l'extérieur du inclus et class_methods blocs