2010-12-07 7 views
3

Während das Internet mit Ressourcen überfüllt ist, die die unzähligen Metaprogrammierungsfunktionen von Groovy preisen, habe ich bis jetzt noch nichts gefunden, was einem umfassenden "Best Practices" Leitfaden für die tatsächliche Verwendung solcher Features nahekommt.Idiomatic MetaProgramming

Abgesehen von den typischen caveat emptor Warnung übermäßigem Gebrauch, die spezifische Ratschlag ich gelesen habe schon sagt Kategorien für Vermehrung metaclasses wenn möglich (das ist wirklich nur eine andere Art der Verstärkung der alten Idiom des Verwendens 'begrenzter Fokus').

Der gesunde Menschenverstand war ausreichend für meine trivialen Projekte, aber ich bin zunehmend besorgt über den Aufbau von potentiell schlechten/inkonsequenten Präzedenzfällen, während ich ehrgeizigere Aufgaben anwende.

So würde ich sehr schätzen alle Ratschläge, Ressourcen oder konkrete Beispiele von Groovy (oder auch Sprache-Agnostiker - Meine zugegebenermaßen kurze Erfahrung mit Ruby ließ mich ähnlich) Metaprogrammierung Best-Practices.

das Thema Um zu klären, werde ich eine (stark) vereinfacht Rational Nummer Projekt zur Verfügung stellen, die metaprogramming auf verschiedene Weise einsetzen könnte:

@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) 

Dennoch versucht 3*new Rational(num:1, den:2) eine Missing produzieren würde offensichtlich.

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

Aber dies ist folglich global wirksam und ziemlich starr:

Die einfachste und wohl am wenigsten zerbrechliche Mittel, um die kommunikative Eigenschaft des Hinzufügens in der Rational-Klasse mit einer statischen Initialisierungsblocks wären.

Ein vielseitiger und vielleicht besser organisiert Ansatz mit irgendeiner Art von optionalem Bootstrapping sei:

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

Jetzt haben wir die Möglichkeit, so dass die Funktion (en) wir haben möchten. Dies könnte jedoch zu allen Arten von Abhängigkeitsproblemen führen.

Und dann Kategorien gibt es .. Sicher explizit, aber nicht gerade bequem:

@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) 
} 

Im Allgemeinen finde ich, ich metaclasses ändern, wenn ich neues Verhalten hinzufügen, aber Kategorien verwenden, wenn ich sein kann Änderung bestehenden Verhaltens. Das Überschreiben des Division-Operators, um beispielsweise eine Fraktion zu erzeugen, wäre am besten innerhalb einer Kategorie enthalten. Daher wären die Lösungen 1 oder 2 "akzeptabel", da ich lediglich das Verhalten der Integer-Klasse anhege, ohne die typische Verwendung zu ändern.

Ist jemand mit dieser Stimmung einverstanden? Oder kennen Sie vielleicht eine überlegene Methodik? (Ich habe Mixins hier weggelassen, das ist mir klar.)

+1

Ich denke viel hängt vom Kontext ab. Ist Ihre Meta-Methode global oder nur in bestimmten Kontexten nützlich?Ich habe festgestellt, dass mein größtes Problem darin besteht, sich zu erinnern, wo eine Metamethode hinzugefügt wurde und ob Sie sie in Ihrem aktuellen Kontext haben. Es gab viele Male, wo ich es als Kategorie verwendete und dann erkannte, dass es global besser in die App passte und es dort als Teil eines Refactors hinzufügte. – dstarh

+0

@dstarh Das macht Sinn - Es war Teil meiner Motivation für die Auflistung der In-Place vs Bootstrapping-Methoden. Ich würde denken, dass die Konsolidierung von Metamethoden zu einer Bootstrap-Klasse der am besten organisierte Ansatz wäre. Ich könnte mich irren, aber ich glaube, das ist der Ansatz, den man normalerweise in Grails findet. Es gibt Ihnen Kontrolle darüber, was wann hinzugefügt wird. Ich mag den Zerbrechlichkeitsfaktor einfach nicht, noch verkompliziere ich einen Build-Vorgang genauso wenig wie ich. Beginnen mit Kategorien, aber weil sie einfacher zu refaktorieren sind, ist sicherlich eine logische Herangehensweise. – Northover

+0

Ich stimme zu, dass es in vielen Fällen am sinnvollsten ist, dies zu tun. Es verhindert interessante Probleme, wie wenn ein anderer Entwickler sich fragt, wo die .toUrl() -Methode zur String-Klasse hinzugefügt wird. Wenn Sie sie an einem Ort zentralisieren, ist das nicht so verwirrend. Vor allem, wenn Sie eine Klasse an eine andere Klasse übergeben können, bei der der referenzierten Klasse eine Metamethode irgendwo im Callstack hinzugefügt wurde und sie dann beispielsweise in der Ansicht verwendet wird. – dstarh

Antwort

1

Es gibt ein Kapitel über Metaprogrammierung in Groovy in Action, das das Thema ziemlich gut behandelt. Achten Sie darauf, die zweite Ausgabe zu bekommen (die gedruckte Version ist noch nicht verfügbar, aber Sie können eine frühe Zugang Freigabe in elektronischem Format erhalten), weil die erste Ausgabe sehr veraltet ist, besonders auf dem Thema der Metaprogrammierung.

+0

Es steht auf meiner Liste. Obwohl laut http://www.manning.com/koenig2/ die entsprechenden "MEAP" -Kapitel noch nicht veröffentlicht wurden, muss dieser vielleicht warten. – Northover

Verwandte Themen