【问题标题】:Groovy metaClass closures vs dynamic mixinsGroovy 元类闭包与动态混合
【发布时间】:2013-06-01 19:29:37
【问题描述】:

所以我想向 JDK 类添加方法,如 InputStream、File 等。我试图找出最好的方法是什么,但似乎有几种选择。一种方法是通过将方法添加到 Class 的 metaClass 属性中,如下所示:

   InputStream.metaClass.copy = { OutputStream out ->
        long total = 0
        byte[] buffer = new byte[8096]
        int len
        while ((len = read(buffer)) >= 0) {
            out.write(buffer, 0, len)
            total += len
        }
        out.flush()
        return delegate
    }

另一种方法是使用这样的动态混合:

class EnhancedInputStream {
    static {
         InputStream.metaClass.mixin( EnhancedInputStream )
    }

    public InputStream copy( OutputStream out ) {
        long total = 0
        byte[] buffer = new byte[8096]
        int len
        while ((len = mixinOwner.read(buffer)) >= 0) {
            out.write(buffer, 0, len)
            total += len
        }
        out.flush()
        return mixinOwner
    }
}

所以第一个问题是动态 Mixins 是否会取代使用 metaClass + Closure 来创建 mixins?动态混合的示例并没有真正详细讨论我能找到的范围规则。这让我想到了下一点。

您可以在第一个使用 metaClass 的代码示例中看到,我使用委托来访问我正在向其中添加方法的类的 this 指针。使用动态 Mixins 的等效方法是什么?我发现的所有例子都是无状态的(真的毫无意义)。我发现一个例子提到了一个可以用来代替委托的特殊成员 mixinOwner。这是真的吗?

其次,您会看到我在 EnhancedInputStream 中使用了一个静态块来动态地将 mixin 添加到 InputStream。使用 metaClass 时,添加这些的最佳方法是什么?另一个带有 import 语句的静态块?

我想我真的只想要一个编译时 Mixin,我可以在 mixin 的源而不是目标上定义 @Mixin,因为我没有编写目标。喜欢

@MixinInto(File)
public class EnhancedFileMixin {
    public void zip( File output ) {
        // .....
    }
}

但这在 Groovy 领域似乎并不存在。那么使用 metaClass 或动态 mixins 实现这一目标的最佳方法是什么?

【问题讨论】:

  • 只是一个想法:使用“访问者”设计模式不是正常的做法吗?此外,您的“复制”对象让我想起了“功能”设计模式......

标签: groovy metaprogramming mixins


【解决方案1】:

好吧,我终于自己想通了。本质上,this 引用指的是对我们没有多大好处的 Mixin 实例。但是,您可以使用“as”关键字将其转换为目标类的实例。例如:

class MyMixin {
   static {
      File mixin MyMixin
   }

   File foo() {
       return this as File
   }
}

File f = new File()
println( f.foo().equals( f ) )

至于jira bug 所指的mixinOwner 和owner 引用。它们不存在。这是获取对添加了 mixin 的实例的引用的唯一方法。

我写了一篇更长的博文,因为我认为这对未来的 Groovy 程序员来说是重要的信息,因为讨论这个的官方文档为零。

http://wrongnotes.blogspot.com/2013/06/groovy-mixins-and-this-pointer.html

【讨论】:

    【解决方案2】:

    很高兴您提出这个问题。回答一个非常重要的问题:

    我想我真的想要一个编译时 Mixin,我可以在 mixin 的源而不是目标上定义 @Mixin,因为我没有写目标。

    @Mixin 无法实现这一点,但我们在 Groovy 中有一些东西可以帮助你。它被称为@Category。让我再次通过您的示例向您展示如何在类别中实际有效地使用this。看看下面的脚本:

    @Category(InputStream)
    class InputStreamCategory{
        def copy(OutputStream out){
            long total = 0
            byte[] buffer = new byte[8096]
            int len
            while ((len = this.read(buffer)) >= 0) {
                out.write(buffer, 0, len)
                total += len
            }
            out.flush()
            return this
        }
    }
    
    class MyUtil{
        def str = 'This is a dummy String!!!!'
    
        InputStream inputS = new ByteArrayInputStream(str.bytes)
        OutputStream outputS = new ByteArrayOutputStream()
    
        def copy(){
            use(InputStreamCategory){
                inputS.copy(outputS)
                println "Printing Output Stream: " + outputS
            }
        }
    }
    
    def util = new MyUtil()
    util.copy()
    
    //Prints:
    Printing Output Stream: This is a dummy String!!!!
    

    说明:-

    @Category(InputStream) 在InputStreamCategory 中设置this,在您的util 类中,您只需将use 新添加的方法copy 设置为InputStream。使用类别的好处是您可以在这种情况下获得调用者对象inputS。传递给类别的第一个参数总是指调用者。您可以为不同的实现设置不同的类别,例如FileCategory 等,然后为use 那些Categories 创建一个实用程序类。因此,您最终会得到zip、copy 等实用程序。

    您可以从 api 获取有关相同内容的详细信息。我也强烈推荐通过Category and Mixin Transformations。

    回答第一个问题:-
    动态 Mixins 是否会取代使用 metaClass + Closure 创建 mixins? 不,他们没有。 metaClass 实现不会创建 Mixin。它只是在运行时在有关该类的元数据注册表中添加另一个方法。您将获得delegate 的句柄。另一方面,@Mixin 让您能够继承预定义的属性。

    【讨论】:

    • 感谢您的回答。我知道类别,但我不想每次我想使用 mixin 时都使用 use 块。我希望它像 GDK 一样增加类。我将实用程序类视为没有像 Groovy 提供给我们的开放类的变通方法。所以现在我有了 mixins,我觉得我不需要实用程序类。现在我没有看到 metaClass 和 mixins 之间的区别。向元数据添加条目与创建 mixin 似乎只是技术实现。如果这是唯一的区别,我看不出在功率方面有任何真正的区别。
    • 您可以同时使用 Category 和 @Mixin。我不想在回答中提及这一点,因为您在我提供的链接中有一个这样的例子。我想证明 Groovy 足够灵活,正如您所期望的那样。
    • 我刚刚重新阅读了您的原始帖子,我认为您提到的一件事确实使 Mixins 的天平倾斜,并且 Mixins 可以从其他 Mixins 继承。元类 mixin 没有这个选项。我要补充一点,Mixins 可以维护自己的实例变量,而 metaClass mixins 很难做到。
    【解决方案3】:

    我猜最接近@MixinInto 的是magic package convention。我无法将它混入界面,但我设法将其混入FileInputStream,如果这适合您的情况。我想您可以使用构造函数中的MetaClass 添加状态。

    写一个类混入InputStream。它必须是:

    • 包内groovy.runtime.metaclass.java.io
    • 命名为FileInputStreamMetaClass(完全正确)
    • 编译并放入类路径
    • 扩展DelegatingMetaClass

    它只能拦截GroovyObject方法,所以不是那么直截了当。如果您喜欢纯粹的动态 groovy,那就太好了:

    package groovy.runtime.metaclass.java.io
    
    class FileInputStreamMetaClass extends DelegatingMetaClass {
      FileInputStreamMetaClass(MetaClass meta) {
        super(meta)
        println "built FileInputStreamMetaClass"
      }
    
      Object invokeMethod(Object delegate, String method, Object[] args) {
        switch (method) {
          case "copy": 
            return "i'm copying stuff"
          default:
            return super.invokeMethod(delegate, method, args)
        }
      }
    }
    

    编译:

    $ groovyc FileInputStreamMetaClass.groovy
    $ groovy -cp . InputTest.groovy
    

    测试:

    InputStream input = new FileInputStream("/tmp/test.tmp")
    assert input.copy() == "i'm copying stuff"
    

    有点麻烦。


    我会在一天中的任何时候去Extensions。三个文件:

    // file META-INF/services/org.codehaus.groovy.runtime.ExtensionModule
    moduleName=InputExtensionModule
    moduleVersion=0.1
    extensionClasses=InputStreamExtension
    

    扩展名:

    class InputStreamExtension {
      static String copy(InputStream input) {
        "copying stuff, doc"
      }
    } 
    

    测试:

    def input = new FileInputStream("/tmp/test.tmp")
    assert input.copy() == "copying stuff, doc"
    

    编译运行:

    $ groovyc InputStreamExtension.groovy
    $ groovy ISExtensionTest.groovy
    

    我认为扩展是使用static { mixin } 块的理想场所。有一些变化:

    class InputStreamExtension {
      static {
        InputStream.mixin InputStreamMixin
      }
    
      static String copy(InputStream input) { "copying stuff, doc" }
    } 
    
    @Category(InputStream)
    class InputStreamMixin {
      Object pop() {
        "input mixin pop"
      }
    }
    

    一个新的测试:

    def input = new FileInputStream("/tmp/test.tmp")
    
    assert input.copy() == "copying stuff, doc"
    assert input.pop() == "input mixin pop"
    

    【讨论】:

    • 感谢扩展讨论。这是向 3rd 方类添加方法的另一种方式。我同意使用扩展来注册 mixin 可能是最好的选择,而不是 mixin 本身内部的静态块。但是,在我看来,用 metaClass、mixin(编译和运行时)、类别、扩展做同样的事情的方法太多了。尽管我承认我没有研究过每个选项的性能,但我仍然觉得运行时 mixin 优于所有这些。我很好奇那会是什么样子。我认为需要进行更多探索才能比较这些选项。
    • 没有关于性能的线索。由于我经常使用数据库,它通常不会影响太多。我知道扩展可以静态编译:-)。这对与我一起工作的人来说是一大优势。
    猜你喜欢
    • 1970-01-01
    • 2013-09-08
    • 2015-11-29
    • 1970-01-01
    • 1970-01-01
    • 2016-09-17
    • 1970-01-01
    • 2013-05-18
    • 2012-07-14
    相关资源
    最近更新 更多