Tuesday, January 11, 2011

Groovyにはアクセスコントロールがない、ではどうするか?

バージョン1.7.5現在、Groovyではアクセス修飾子が機能しません。これらはコメント同様、注意を促す役割しか持ちません。次の例を見てください。アクセス修飾子がその名を偽っていないのなら、このクラスの全てのフィールドとメソッドへのアクセスは制限されるはずです。
----
package foo

class Foo {
    protected protectedField
    @PackageScope packagePrivateField
    private privateField

    protected protectedMethod() { }
    private privateMethod() { }
}
----

Groovyが期待どおりにGroovyコードをJavaバイトコードにコンパイルしているか確認する為に、classファイルを逆アセンブルしてみましょう。
----
javap -private Foo
----

結果はこうなりました。問題ありません。完璧です。期待どおり。
----
public class Foo extends java.lang.Object implements groovy.lang.GroovyObject{
    protected java.lang.Object protectedField;
    java.lang.Object packageScopeField;
    private java.lang.Object privateField;
    :
    protected java.lang.Object protectedMethod();
    private java.lang.Object privateMethod();
}
----

しかしこれらへのアクセスは制限されません。次の例のようにどこからでもアクセスできます。
----
package bar

def foo = new Foo()
foo.protectedField
foo.packagePrivateField
foo.privateField
foo.protectedMethod()
foo.privateMethod()
----

http://jira.codehaus.org/browse/GROOVY-1875によればこのバグは2.0までに直るようです。では今できることは?

命名規則はどうでしょうか? 事故から守るにはこれでほぼ充分かもしれません。例えばPythonやPerlのようにアンダースコアのプレフィクスを使います。
----
protected _protectedField
@PackageScope __packageScopeField
private ___privateField

protected _protectedMethod()
private ___privateMethod()
----

汚いし、複雑です。使いたくありません。

クロージャはどうでしょうか? もし必要となるのがprivateスコープだけなら、クロージャを使うことで実現できます。
----
package foo

class SecretiveFoo {
  
    SecretiveFoo() {
        def privateField;
        getPrivateField = {
            privateField  
        }
        setPrivateField = { newValue ->
            privateField = newValue
        }
      
        def privateMethod = {
            println 'private method called'
        }
        publicMethod = {
            privateMethod.call()
            // do others
        }
    }
  
    public getPrivateField
    public setPrivateField
    public publicMethod
}
----

----
package bar

def secretiveFoo = new SecretiveFoo()
secretiveFoo.setPrivateField(1)
println secretiveFoo.getPrivateField()
secretiveFoo.publicMethod()
----

結果はこうなります。
----
1
private method called
----

ただし、このやり方には高いコストがかかります。次のプログラムを動かすとわかります。Transparentは通常のGroovyクラスです。Serectiveはこのやり方をシミュレートするクラスで、指定された数だけprivateフィールドにアクセスするクロージャを生成します。これらを1000回インスタンス化するコストを計測します。
----
class Secretive {
  
    Secretive(n) {
        def privateField = 0
        actions = []
        (0..n).each {
            actions << { privateField }  
        }
    }
  
    def actions
}

class Transparent {
  
    private privateField = 0
  
}

def maxmem = Runtime.runtime.maxMemory()
def memBefore = maxmem - Runtime.runtime.freeMemory()
def timeBefore = System.nanoTime()
def list = []
(0..<1000).each {
    list << new Secretive(10)
    // list << new Transparent()
}
def timeAfter = System.nanoTime()
def memAfter = maxmem - Runtime.runtime.freeMemory()
def diff = memAfter - memBefore
println "time usage: ${ (timeAfter - timeBefore) * 0.000001 } ms"
println "memory usage: ${ (memAfter - memBefore) * 0.001 } kb"
----

次の結果が出ました。重すぎます。使えません。

Transparent
Secretive(10)
Serective(20)
Serective(30)
Average Time Usage (ms)
17.26
62.83
75.66
87.15
Average Memory Usage (kb)
1133.67
2706.19
3720.96
5073.95

2.0のリリースをひたすら待つしかないんでしょうか。

誰か案あります?

      

No comments:

Post a Comment