2010-12-07 19 views
3

Alors que le web regorge de ressources vantant les innombrables capacités de métaprogrammation de Groovy, je n'ai pas encore trouvé quelque chose de proche d'un guide complet des «meilleures pratiques» pour l'utilisation réelle de ces fonctionnalités.Métaprogrammation idiomatique

En dehors de la mise en garde typique emptor avertissement d'utilisation excessive, la pièce la plus spécifique de conseil que j'ai lu suggère d'utiliser les catégories en faveur des métaclasses AUGMENTER lorsque cela est possible (ce qui est vraiment juste une autre façon de renforcer l'ancien idiome de 'Portée limitée').

Le bon sens a suffi à mes projets insignifiants, mais je m'inquiète de plus en plus de construire à partir de précédents potentiellement médiocres/incohérents, alors que je m'attaque à des tâches plus ambitieuses. Ainsi, j'apprécierais grandement tout conseil, ressources, ou exemples concrets de Groovy (ou même de langue agnostique - Mon expérience certes brève avec Ruby m'a laissé me ressembler) des meilleures pratiques de métaprogrammation.

Pour clarifier le sujet, je vais vous donner un nombre rationnel simplifié (très) projet qui pourrait employer metaprogramming de plusieurs façons différentes:

@Immutable class Rational{ 
    int num, den 

    Rational multiply(Integer v){ 
     new Rational(num:num*v, den:den) 
    } 
} 
assert new Rational(num:1, den:2) * 3 == new Rational(num:3, den:2) 

Pourtant, la tentative 3*new Rational(num:1, den:2) produirait évidemment un MissingMethodException.

Le plus simple, et des moyens sans doute les moins fragiles d'ajouter la propriété communication serait avec un bloc d'initialisation statique dans la classe rationnelle:

static { 
    Integer.metaClass.multiply = {Rational fraction -> fraction*delegate} 
} 
... 
assert 3*new Rational(num:1, den:2) == new Rational(num:3, den:2) 

Mais c'est par conséquent global en vigueur, et plutôt rigide.

Un plus polyvalent, et peut-être approche plus organisée serait avec une sorte de bootstrapping en option:

class BootStrap{ 
    static void initialize(){ 
     Integer.metaClass.multiply = {Rational fraction -> fraction*delegate} 
    } 
} 

Maintenant, nous avons la possibilité d'activer la fonction (s) nous souhaitons avoir. Ceci, cependant, pourrait entraîner toutes sortes de problèmes de dépendance.

Et puis il y a des catégories .. en toute sécurité explicite, mais pas exactement pratique:

@Category(Integer) class RationalCategory{ 
    Rational multiply(Rational frac){ 
     frac*this 
    } 
} 
use(RationalCategory){ 
    assert 3*new Rational(num:1, den:2) == new Rational(num:3, den:2) 
} 

En général, je trouve que je modifie métaclasses quand je suis ajouter nouveau comportement, mais utiliser les catégories quand je peux être en changeant comportement existant. Surpasser l'opérateur de division pour produire une fraction, par exemple, serait mieux contenu à l'intérieur d'une catégorie. Ainsi, les solutions 1 ou 2 seraient «acceptables» car j'applique simplement un comportement à la classe Integer, sans modifier l'utilisation typique.

Est-ce que quelqu'un est d'accord/pas d'accord avec ce sentiment? Ou peut-être connaître une méthodologie supérieure? (J'ai omis des mixins ici, je me rends compte.)

+1

Je pense que beaucoup dépend du contexte. Est-ce que votre méthode meta est utile globalement ou seulement dans des contextes spécifiques.J'ai trouvé que mon plus gros problème est de me souvenir où une méta méthode a été ajoutée et si vous l'avez dans votre contexte actuel. Il y a eu de nombreuses fois où je l'ai utilisé comme une catégorie et ensuite réalisé que c'était un meilleur ajustement globalement dans l'application et l'a ajouté là dans le cadre d'un refactor. – dstarh

+0

@dstarh Cela a du sens - Cela faisait partie de ma motivation pour lister les méthodologies in-situ et bootstrap. Je pense que la consolidation des métaméthodes à une classe de bootstrap serait l'approche la plus organisée. Je peux me tromper, mais je crois que c'est l'approche généralement utilisée dans Grails. Il vous donne le contrôle de ce qui est ajouté et quand. Je n'aime pas le facteur de fragilité, ni compliquer une procédure de construction plus que je ne le dois. Commencer par les catégories si mais parce qu'elles sont plus faciles à refactoriser est certainement une approche logique. – Northover

+0

Je suis d'accord qu'il est plus logique dans de nombreux cas de le faire. Cela évite des problèmes intéressants comme lorsqu'un autre développeur se demande où la méthode .toUrl() est ajoutée à la classe String. Si vous les centralisez dans un endroit, ce n'est pas aussi confus. Surtout quand vous pouvez passer une classe à une autre classe où la méthode référencée a une méta méthode ajoutée quelque part dans la pile d'appels et ensuite elle est utilisée par exemple dans la vue. – dstarh

Répondre

1

Il y a un chapitre sur la métaprogrammation dans Groovy in Action, qui couvre assez bien le sujet. Assurez-vous d'obtenir la deuxième édition (la version imprimée n'est pas encore disponible, mais vous pouvez obtenir une version d'accès en format électronique), car la première édition est très obsolète, en particulier sur le sujet de la métaprogrammation.

+0

C'est sur ma liste. Bien que, selon: http://www.manning.com/koenig2/ les chapitres "MEAP" pertinents doivent encore être publiés, alors celui-ci devra peut-être attendre. – Northover