2016-07-26 38 views
0

现在,我在这堵墙上撞了我一个多星期,让我简单地解释我想要达到的目标。如何从闭包代码中访问闭包属性?

我有这样的(简单的例子)定义的DSL

TestingSpec 
    .newInstance() 
    .sayHello() --> print "Hello "+something 
    .sayGoddbye() --> print "Goddbye"+something 

注意在那里something使不通过任何地方故意的,因为我希望通过将限制使用我的DSL的范围用户,所以我真正想要的是以编程方式在该实例中“注入”something。这是通过使用闭包完成的。

Myscript.execute({ 
    TestingSpec 
     .newInstance() 
     .sayHello() --> print "Hello "+something 
     .sayGoddbye() --> print "Goddbye"+something 
}) 

MyScript将由something值传递给它调用它,无论是通过传递一个参数,或通过添加属性或通过添加绑定(不知道什么是最好的,真的)

close.call("Toni") 

close.metaClass.something = "Toni" 

def binding = new Binding() 
binding.setVariable("something", "Toni") 
close.setBinding(binding) 

但是现在我陷入了我认为这很容易的问题,我如何从TestingSpec代码访问Closure something?例如

public newInstance() { 
    def instance = new TestingSpec() 
    instance.something = *MY CLOSURE SOMETHING* 
    return instance 
} 

我试过几个选项,比如与thisowner和闭合的delegate搞乱,但不能这样做。

任何提示将非常欢迎...

干杯。

回答

1

我不认为这是可行的。 TestingSpec在堆栈上太高以至于无法知道他是从关闭中调用的。我想推荐两种解决方案

1.以编程方式通过somethingTestingSpec

Myscript.execute({ 
    TestingSpec 
     .newInstance(something) 
     .sayHello() --> print "Hello "+something 
     .sayGoddbye() --> print "Goddbye"+something 
}) 

2.进行TestingSpec例如,通过MyScript

我喜欢这个解决方案更被创建。它是关于MyScript.execute({})负责创建和处理的TestingSpec生命周期:

class TestingSpec { 
    def something 
    def sayHello() { 
     println "Hello " + something 
     this 
    } 
    def sayGoddbye() { 
     println "Goddbye " + something 
     this 
    } 
} 

class Myscript { 
    static TestingSpec testingSpec 
    static execute(closure) { 
     def binding = new Binding(something: 'test for echo') 
     testingSpec = new TestingSpec(something: binding.something) 
     binding.testingSpec = testingSpec 
     closure.binding = binding 
     closure() 
    } 
} 

Myscript.execute({ 
    testingSpec // this is an instance, and not a static call 
     .sayHello() // print "Hello "+something 
     .sayGoddbye() // print "Goddbye"+something 
}) 

输出:

$ groovy Spec.groovy 
Hello test for echo 
Goddbye test for echo 

3.代表封盖testingSpec

如果设置了封闭的委托到testingSpec,你可以直接引用testingSpec来调用它的方法,提供一个非常干净的代码。注意:我还更新了例如除去MyScript

class TestingSpec { 
    static TestingSpec testingSpec 
    static execute(closure) { 
     def binding = new Binding(something: 'test for echo') 
     testingSpec = new TestingSpec(something: binding.something) 
     binding.testingSpec = testingSpec 
     closure.delegate = testingSpec 
     closure.binding = binding 
     closure() 
    } 

    def something 
    def sayHello() { 
     println "Hello " + something 
     this 
    } 
    def sayGoddbye() { 
     println "Goddbye " + something 
     this 
    } 

} 

TestingSpec.execute({ 
    sayHello() // print "Hello "+something 
    sayGoddbye() // print "Goddbye"+something 
}) 

4。注入testingSpec作为参数传入你的闭合

根据您的答案,一个简单的建议:考虑给你已经建立自己的用户testingSpec变种,因为它允许更多的控制和定制给你。想控制的一个简单的反转(这允许testingSpec看到在封闭的结合动态变量):

.withDistinctNames({ testingSpec -> 
    testingSpec 
     .from(name) 
     .withAddress(email) 
     .sendEmail(template) 
}) 

有了一个完整的测试:

class TestingSpec { 
    def commands = [] 
    static newInstance(listOfNames) { 
     new TestingSpec() 
    } 

    def sayHello() { commands << "sayHello"; this } 
    def waveGoddbye() { commands << "waveGoddbye"; this } 
    def withAddress(address) { commands << "withAddress $address"; this } 
    def sendEmail(template) { commands << "sendEmail $template"; this } 

    def withDistinctNames(closure) { 
     commands << "withDistinctNames" 
     closure.binding = new Binding(address: "sunset boulevard", template: 'email is $email') 
     closure(this) 
     this 
    } 
} 

test = TestingSpec 
    .newInstance([:]) 
    .sayHello() 
    .withDistinctNames({ me -> 
     me 
      .withAddress(address) 
      .sendEmail(template)   
    }) 
    .waveGoddbye() 

assert test.commands == [ 
    'sayHello', 
    'withDistinctNames', 
    'withAddress sunset boulevard', 
    'sendEmail email is $email', 
    'waveGoddbye' 
] 
+0

您好,感谢您的回复。第一种解决方案就是我现在正在做的事情,但试图避免,我使用'close.call(“Toni”)'并将其引用为'TestingSpec.newInstance(it)'。第二种解决方案我没有考虑它,恐怕会更复杂,因为在我的“真实”情况下,Myscript和TestingSpec都是一样的。这个想法实际上是将一个参数从外部的TestingSpec传递给内部(在闭包中)TestingSpec。我现在要去尝试。 – amsmota

+0

@amsmota将“TestingSpec”和“MyScript”作为同一个类对于第二种解决方案来说并不是问题,我写了第三个解决方案,其特点是具有相同的类和闭包委托。另外,考虑在你以前提出的问题中接受一些答案。 – Will

+0

嗨@WillP,再次感谢您的建议。恐怕我没有正确解释自己,主要是因为我是Groovy的新手。所以我不应该像使用MyScript一样使用“script”这个词,因为这是从Java调用而不是groovy脚本的类。所以(我后来才明白这一点)我不能使用绑定选项。然而,你的建议1和3使我提出了一个并不理想的解决方案,但它的“优雅”足以让我的DSL的潜在用户“容易”理解,我将把它作为我的问题的答案。 – amsmota

0

我终于达成解决我的问题,虽然不理想的是它“优雅”,足以让我的DSL的潜在用户“容易”理解。所以,我想要做的就是让DSL用户这样写:

TestingSpec 
    .newInstance() 
    .from(listOfNames) 
    .sayHello() 
    .withDistinctNames({ 
     TestingSpec 
      .newInstance() 
      .from(*****) <---- problem 
      .withAddress() 
      .sendEmail(template)   
    }) 
    .waveGoddbye() 

可能有一些方法,基本上充当过滤器+循环,所以在这个例子中它会将名称过滤为唯一名称,然后依次将Closure应用于每个名称。当然,这种做法是非常容易的通过只是在做

.withDistinctNames({ 
     TestingSpec 
      .newInstance() 
      .from(it) 
      .withAddress() 
      .sendEmail(template)   
    }) 

或更复杂的情况

.withDistinctNames({ name, email, template -> 
     TestingSpec 
      .newInstance() 
      .from(name) 
      .withAddress(email) 
      .sendEmail(template)   
    }) 

的概率是我不知道谁去使用这个DSL做,也可以不是熟悉闭包,参数,功能等等的人,甚至可以是来自组织外部的人,所以我的目标是通过将所需的变量从外部Spec传递到内部Spec来保持简单。在我TestingSpec实现我会:

public TestingSpec withAll(Closure k, Closure f) { // withAll is used by all with* methods 
    def result = k.call(root) // root is the entire listOfNames 
    def results = result 
     .collect { 
      // HERE'S THE PROBLEM, HOW TO INJECT ALL THE PARAMETERS INTO THE INNER SPEC? 
      f.call(it) // this line passes vars to the Closure, not the inner Spec, and I found no way for the Closure itself to inject them in the Spec 
     } 
    return this; 
} 

后,我看到它是不可能的注入在内部规范本身PARAMS(即尚未被实例化),我试图将它们传递到封闭,并从那里到规格,像这样:

.withDistinctNames({ 
     TestingSpec 
      .newInstance() 
      .from(this) <------------------- 
      .withAddress() 
      .sendEmail(template)   
    }) 

我推测,这是包含我需要的所有信息外规格,但事实并非如此。我试着用这个this.thisObjectthis.ownerthis.delegate

不过,以上建议第三的帮助下,我终于实现了这一点:

public TestingSpec withAll(Closure k, Closure f) { 

    f = f.rehydrate(f.getDelegate(), f.getOwner(), this) 

    def result = k.call(root) 
    def results = result 
     .collect { 
      this.parameters = [whatever I need] 
      f.call() 
     } 
    return this; 
} 

这样,在DSL实际上是外部规格,所以用户现在可以使用更直观的“this”关键字。为了避免混淆,我摆脱了。从()方法,像这样:

TestingSpec 
    .newInstance(listOfNames) 
    .sayHello() 
    .withDistinctNames({ 
     TestingSpec 
      .newInstance(this) // my newInstance implementation can parse all the parameters as it wishes 
      .withAddress() 
      .sendEmail(template)   
    }) 
    .waveGoddbye() 

虽然它不是理想的足够接近,从潜在用户的角度来看,我认为。如果有人有建议请让我知道。

感谢Will P和所有人。

+0

哇!这太棒了!我很高兴你找到了解决方案!我已经用一个更多的建议更新了我的答案。希望能帮助到你。 – Will

0

最近研究了一个类似Groovy的小型DSL工具,并且挖掘了一下griffon和swing以及其他材料,我现在以下列结尾,作为另一个建议提交给答案。 任何意见/建议将不胜感激。


IMHO域模型(一个模型类),并且支持类之间的明确分离(建筑物,聚集,上下文共享,和过滤部分的模式,至少一个生成器类)是关键要素之一 有一个简单而有效的模型。这是groovy swing和griffon中使用的模式,显示出非常灵活。

它允许构造与各{}与{}收集操作这是干净的,足够的理解, 和大多没有链接操作(即在结尾处添加一个恼人的回报这个每个操作)。

像这里例如(末尾的源代码,MSys的是门面至底层系统):

MSys.with { 
     model.each { 
      it.sayHello 
      it.sayGoodBye 
      it.setAddress randomAddress(it.name); 
      it.sendEmail 
     } 

     println " Hello counts" 
     model.each { 
      def key = it.name 
      def value = MSys.context[key] 
      println "Counter for ${key} = ${value}" 
     } 
    } 

MSys.model.each { 
     it.with { 
      sayHello 
      sayGoodBye 
      setAddress randomAddress(name) ; 
      sendEmail 
     } 
    } 

MSys.eachModelDo { 
     sayHello 
     sayGoodBye 
     setAddress randomAddress(it.name); 
     sendEmail 
    } 

或... (很多可能性)

它也允许有一些共享的情况下很容易(在本例中,方面是所有的模型元素,几乎什么事情都可能放,连接信息,用户偏好,缓存等共享的地图), 并通过将其放在另一个班级/脚本中来隐藏所有的锅炉板。

的职责是:

  • SpecModel:领域模型:问好,道别,属性等

  • SpecBuilder:(在这个例子从一个列表)创建的模型,持有共享上下文(地图)并最终处理一些操作的关闭代表上下文

这种分离对于一方面处理设置操作和另一方面处理实体(模型)操作很重要。 除了一个建设者,从用户POV它是一个门面

并从开发人员POV BTW它应该是一个门面几个类,包括建设者 - 但更好的开始简单。

下一个步骤将是FactoryBuilderSupport在这个生成器,以便从Groovy的DSL建设者设施整合中受益,但是这一个大步超越我不舒服与尚未......(WIP现在)


很少Groovy的重述,你肯定已经知道了,但问题的一部分,如果你想有一个光语法:

  • 在Groovy ;()是可选的,除非有冲突。

即使没有直接的冲突,代表团策略并不总是直接和 运行时错误并不总是很清楚(见下文关闭文档链接)

  • 具有公共getter方法性能,允许像访问方法中的用途的性质没有(), 的获取和设置的前缀可推断

即具有类似方法

public getSendEmail() { 
    println "email for ${name}" 
} 

,你可以使用语法:

myObject sendEmail 

Groovy的假定属性的getter调用和做休息。 这有助于在dsl语法中删除()

  • 的背景是封闭的委托范围内没有

当您使用关闭,您使用引用您在关闭工作的对象 (即消息的接收者)。

实施例:

[1, 2, 3].each { println it } 

是确定

如果haved使用

[1, 2, 3].each { println this } 

你将不得不打印到外部对象的引用,在常规控制台控制台

(其中IIRC是你在第一篇文章中遇到的问题之一)

这在Groovy Closures DOC被很好的解释:

(摘录:)

  • :其中封闭件所限定的封闭类
  • 所有者:包围物体,其中关闭被定义,类或关闭
  • 委托人:第三方对象w此方法的调用或属性每当消息的接收者没有定义

消息代表团策略(同一文件中解释)的解决并不总是直接的,这是你真正的问题,我认为。


也就是说,DSL中的另一个关键点是用户POV的一致性。 这意味着数据访问模式的一致性,这里标准的Groovy集合(列表,地图每个{}等)可以帮助很多。 加上它可以是一个巨大的优势,为电力用户

MSys.eachModelDo { 
     sayHello 
     sayGoodBye 
     setAddress randomAddress(it.name); 
     sendEmail 
    } 

NB:eachModelDo是很简单的,但有点棘手调试

在一个点上它“

语法糖的方法,如eachModelDo可以很容易地在建筑工地/门面类完成工作“好,没有访问正确的变量:(

我有这样的感觉,有什么不对(?)或至少应该改善(评论欢迎)

/** 
* syntactic sugar 
* direct access without 'it' (optional) 
*/ 
public SpecBuilder eachModelDo(closure) { 
    model.each { 
     closure.delegate = it; 
     closure(it) 
    } 
} 

贝娄是一个小测试,我做了,你可以剪切和在常规控制台

只有部分可见的用户应该是方法演示粘贴的源代码。运行() 所有其他的东西应该被隐藏

任何意见,欢迎


/** 
* The builder build Specs and defines utility methods, filters 
* It shares its context with all elements in the domain 
*/ 
class SpecBuilder { 
    /** the context will be shared with all domain model objects */ 
    private context 
    /** model = all the domain model objects */ 
    private model 

    /** getters/setters */ 
    public getModel() { return model } 

    public getContext() { 
     return context 
    } 

    /** constructors and helpers */ 
    public SpecBuilder(aContext) { 
     context = aContext 
    } 
    /** Default constructor forbidden */ 
    private SpecBuilder() {} 

    public from(aList, closure) { 
     from(aList); 
     model.each { closure(it) } 
     return this 
    } 

    public from(aList) { 
     model = aList.collect { new SpecModel(it, context) } 
     return this 
    } 



    /* TODO filters etc */ 


    /** stats: print counters */ 
    public stats() { 
     println " Hello counts" 
     model.each { 
      def key = it.name 
      def value = this.context[key] 
      println "Counter for ${key} = ${value}" 
     } 
    } 
    /** 
    * syntactic sugar 
    * direct access without 'it' (optional) 
    */ 
    public SpecBuilder eachModelDo(closure) { 
     model.each { 
      closure.delegate = it; 
      closure(it) 
     } 
    } 
} 

/** 
* The Spec Domain Model 
*/ 
class SpecModel { 

    /** the shared context */ 
    private context; 
    /** other properties */ 
    private name; 
    public address; 

    /** getters and setters */ 
    public getName() { return name } 
    public void setAddress(a) { address = a } 
    public getAddress() { return address } 
    public sayHello() { return getSayHello } 
    public sayGoodBye() { return getSayGoodBye } 
    public sendEmail() { return getSendEmail } 

    /** constructors */ 
    public SpecModel(aName, aContext) { 
     name = aName 
     context = aContext 
    } 

    /** Default constructor forbidden */ 
    private SpecModel() {} 

    /** method used like properties, without 'get' and()*/ 
    public getSayHello() { 
     println "(!) hello ${name}" 
     context[name] = context.get(name,0) +1; 
    } 
    public getSayGoodBye() { 
     println "goodBye ${name} !" 
    } 
    public getSendEmail() { 
     println "email for ${name}" 
     if (address) 
      println "Address ${address}" 
    } 
    public getPrintContext() { 
     println context 
    } 
    /** 
    * Returns info to caller 
    */ 
    public gatherInfo() { 
     "info for ${name} : ${new java.util.Random().nextInt(50000)}" 
    } 


} 

class Demo { 

    // several Groots here to test uniques ... 
    def customers = ['Groot', 'Gamora', 'Groot', 'Groot', 'Groot', 'Star-Lord'] 

    /** 
    * Utility function who generates a random address 
    * @param name will prefix the address 
    * @return the address 
    */ 
    public randomAddress(def name) { 
     // good places ... :) 
     def places = [ 
       "Grande Rue", 
       "Cours Emile Zola", 
       "Place Antonin Poncet", 
       "Rue de la République", 
       "Boulevard de la Croix Rousse", 
       "Place Bellecour" 
     ] 
     def random = new java.util.Random(); 
     return new StringBuilder().append(name).append(" ... ") 
         // why not 42? 
         .append(random.nextInt(155)).append(" ") 
         .append(places[random.nextInt(places.size())]) 
       .toString(); 
    } 

    /** 
    * ======================== Main user program ========================= 
    */ 
    def run() { 
     /** the shared context */ 
     def context = [:].asSynchronized() // In case of multi threading access 

     /** The whole system From a user POV : a big façade */ 
     def MSys = new SpecBuilder(context).from(customers) ; 


     println "*** 1 ==================== " 
     /** First form */ 
     MSys.model.each { 
      it.with { 
       sayHello 
       sayGoodBye 
       setAddress randomAddress(name) ; 
       sendEmail 
      } 
     } 
     /** other forms 
     * MSys.with{ is handy 
     * one could write MSys... on each line 
     */ 
     MSys.with { 

      println "*** 2 ==================== " 
      model.each { it.sayHello }; 

      println "*** 3 ==================== " 
      model.with { println " a Model entry = ${it.name} + ${it.address}" } 

      println "*** 4 ==================== " 
      /** false not to mutate the model !!! */ 
      model.unique(false, { a, b -> a.name <=> b.name }).each { it.sayHello } 

      println "*** 5 ==================== " 
      context['aKey'] = 42 
      // verify that each entity has the same context 
      model.with { println " a shared context for ${it.name} : " + it.context } 

      println "*** Stats ================ " 
      /** stats on the shared context */ 
      stats() 
     } 

     println "*** 6 Info to process ======== " 

     /** Gather info to process (addresses)*/ 
     def data = MSys.model.inject([:]) { result, entity -> 
      result[entity.name] = entity.address 
      result 
     } 
     println data 

     MSys.with { 

      println "*** 7 ==================== " 
      model.each { 
       it.sayHello 
       it.sayGoodBye 
       it.setAddress randomAddress(it.name); 
       it.sendEmail 
      } 

      println "*** 8 ==================== " 
      println " Hello counts" 
      model.each { 
       def key = it.name 
       def value = MSys.context[key] 
       println "Counter for ${key} = ${value}" 
      } 
     } 
     println "*** 9 ==================== " 
     MSys.eachModelDo { 
      sayHello 
      sayGoodBye 
      setAddress randomAddress(it.name); 
      sendEmail 
     } 

    } 
} 

new Demo().run() 

/* end of script */ 
+0

Uau,在那里有很多信息要消化,我会在周末仔细观察。感谢您的输入。 – amsmota

+0

这对我来说只是一个在这个DSL主题上的WIP,我也会对你的输入非常感兴趣。顺便说一句,我建议你谷歌保罗国王和纪尧姆拉福格groovy DSL,你会发现很多东西。 :) TIA – ARA