그루비의 동적 객체지향 - 1

Language/Groovy 2016. 9. 9. 10:16

그루비에서 필드 변수의 기본 접근 영역은 특별하게 다루어진다.


1. 필드를 정의할 때 접근 제한자를 지정하지 않으면 이름에 맞는 ‘프로퍼티’가 만들어진다.(geter,seter)



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class SomeClass {
     
    public    public_var='old_public_var_val'  
    String    string_var='old_string_var_val'
    def       def_var   ='old_def_var_val'//필드를 정의할 때 접근 제한자를 지정하지 않으면 이름에 맞는 ‘프로퍼티’가 만들어진다.(geter,seter)
    static    static_var='old_static_var_val'
    protected protected_var1, protected_var2, protected_var3
    private   assignedField = new Date()
    public static final String CONSTA = 'a', CONSTB = 'b'
     
 
}
    SomeClass somclass = new SomeClass();
    println 'b_somclass.public_var : '+somclass.public_var;
    println 'b_somclass.string_var : '+somclass.string_var;
    println 'b_somclass.def_var : '+somclass.def_var;
     
    println '';
    somclass.public_var = 'new_public_var_val';
    somclass.string_var = 'new_string_var_val';
    somclass.def_var = 'new_def_var_val';
    println '';
     
     
    println 'a_somclass.public_var : '+somclass.public_var;
    println 'a_somclass.string_var : '+somclass.string_var;
    println 'a_somclass.def_var : '+somclass.def_var;
     
     
    println '';
    println SomeClass.class.methods.name.grep(~/[get].*/);//get으로 시작하는 메서드이름가져와보자
    println SomeClass.class.methods.name.grep(~/[set].*/);//set으로 시작하는 메서드이름가져와보자
        
    /* 결과
        b_somclass.public_var : old_public_var_val
        b_somclass.string_var : old_string_var_val
        b_somclass.def_var : old_def_var_val
         
         
        a_somclass.public_var : new_public_var_val
        a_somclass.string_var : new_string_var_val
        a_somclass.def_var : new_def_var_val
         
        [getMetaClass, this$dist$invoke$2, this$dist$set$2, this$dist$get$2, getString_var, getDef_var, getStatic_var, getProperty, equals, toString, getClass]
        [setMetaClass, this$dist$invoke$2, this$dist$set$2, this$dist$get$2, super$1$wait, super$1$wait, super$1$wait, super$1$toString, super$1$notify, super$1$notifyAll, super$1$getClass, super$1$equals, super$1$clone, super$1$hashCode, super$1$finalize, setString_var, setDef_var, setStatic_var, setProperty, equals, toString]
    */


위결과를 보면 자동으로 생성된 프로퍼티는 getString_var, getDef_var, getStatic_var setString_var, setDef_var, setStatic_var 가생성된걸 볼수있다. 




2. 그루비는 자바의 접근제한자를 쓸수있다?


1
2
3
4
5
6
7
8
package _2.sub
 
class GClass {
    private private_var='private_var';
    public public_var='public_var';
    protected protected_var='protected_var';
    def def_var ='def_var';
}



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package _2
 
import _2.sub.GClass;
GClass g = new GClass();
println "${g.private_var.class}    :    ${g.private_var}";
println "${g.public_var.class}    :    ${g.public_var}"; //자바에서는 이것만 허용이 될것이다.
println "${g.protected_var.class}    :    ${g.protected_var}";
println "${g.def_var.class}    :    ${g.def_var}";
/*
결과
class java.lang.String    :    private_var
class java.lang.String    :    public_var
class java.lang.String    :    protected_var
class java.lang.String    :    def_var
*/


접근제한자를 썼지만 자유롭게 접근이 가능하다. 


3. 배열 첨자 연사자로 필드 변수 사용하기


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package _3
 
class SomeClass {
     
    public    public_var='old_public_var_val'  
    String    string_var='old_string_var_val'
    def       def_var   ='old_def_var_val'//필드를 정의할 때 접근 제한자를 지정하지 않으면 이름에 맞는 ‘프로퍼티’가 만들어진다.(geter,seter)
    static    static_var='old_static_var_val'
    protected protected_var1, protected_var2, protected_var3
    private   assignedField = new Date()
    public static final String CONSTA = 'a', CONSTB = 'b'
     
 
}
    SomeClass somclass = new SomeClass();
    println 'b_somclass.public_var : '+somclass['public_var'];
    println 'b_somclass.string_var : '+somclass['string_var'];
    println 'b_somclass.def_var : '+somclass['def_var'];
     
    println '';
    somclass['public_var'] = 'new_public_var_val';
    somclass['string_var'] = 'new_string_var_val';
    somclass['def_var'] = 'new_def_var_val';
    println '';
     
     
    println 'a_somclass.public_var : '+somclass['public_var'];
    println 'a_somclass.string_var : '+somclass['string_var'];
    println 'a_somclass.def_var : '+somclass['def_var'];
     
     
    println '';
 
    /* 결과
    b_somclass.public_var : old_public_var_val
    b_somclass.string_var : old_string_var_val
    b_somclass.def_var : old_def_var_val
     
     
    a_somclass.public_var : new_public_var_val
    a_somclass.string_var : new_string_var_val
    a_somclass.def_var : new_def_var_val
    */




4. 필드 접근 메커니즘 확장하기


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package _4
 
class SomeClass {
     
    public    public_var='old_public_var_val'  
    String    string_var='old_string_var_val'
    def       def_var   ='old_def_var_val'//필드를 정의할 때 접근 제한자를 지정하지 않으면 이름에 맞는 ‘프로퍼티’가 만들어진다.(geter,seter)
    static    static_var='old_static_var_val'
    protected protected_var1, protected_var2, protected_var3
    private   assignedField = new Date()
    public static final String CONSTA = 'a', CONSTB = 'b'
     
    def notVar_GetCallCount=0;
    def notVar_SetCallCount=0;
     
    Object get (String name) {
        notVar_GetCallCount++;
        println "get : ${name}";
        return 'pretend value'
    }
    void set (String name, Object value) {
        notVar_SetCallCount++
        println "set  name : ${name},   value : ${value}";
    }
 
}
    SomeClass somclass = new SomeClass();
    println 'b_somclass.public_var : '+somclass.public_var;
    println 'b_somclass.string_var : '+somclass.string_var;
    println 'b_somclass.def_var : '+somclass.def_var;
    println 'b_somclass.not_var: '+somclass.not_var;
     
    println '';
    somclass.public_var = 'new_public_var_val';
    somclass.string_var = 'new_string_var_val';
    somclass.def_var = 'new_def_var_val';
    somclass.not_var = 'new_not_var_val';
    println '';
     
     
    println 'a_somclass.public_var : '+somclass.public_var;
    println 'a_somclass.string_var : '+somclass.string_var;
    println 'a_somclass.def_var : '+somclass.def_var;
    println 'a_somclass.not_var : '+somclass.not_var;
     
     
    println '';
    println 'somclass.notVar_GetCallCount   :   ' + somclass.notVar_GetCallCount;
    println 'somclass.notVar_SetCallCount   :   ' + somclass.notVar_SetCallCount;
        
    /* 결과
        b_somclass.public_var : old_public_var_val
        b_somclass.string_var : old_string_var_val
        b_somclass.def_var : old_def_var_val
        get : not_var
        b_somclass.not_var: pretend value
         
        set  name : not_var,   value : new_not_var_val
         
        a_somclass.public_var : new_public_var_val
        a_somclass.string_var : new_string_var_val
        a_somclass.def_var : new_def_var_val
        get : not_var
        a_somclass.not_var : pretend value
         
        somclass.notVar_GetCallCount   :   2
        somclass.notVar_SetCallCount   :   1
 
    */


여기서 중요하게 볼것이 존재하지 않는 프로퍼티를 접근할 때 get 이라는 메서드를 통하여 접근한다 
존재하지 않는 프로퍼티에 변수값을 지정할 때 set(‘name’,Object)로 접근한다는것도 잊지말어라. 


메서드와인자


자바의 접근제한자를 쓸수 있으며 리턴형은 생략가능하다. 접근제한자나 리턴형을 지정하지 않을 때는 키워드 def를 사용한다. 
def를 쓰면 리턴값의 자료형이 지정되지 않았다고 생각할수 있다 (물론, 리턴이 없는 void메서드일수도 있다) 
내부적으로 java.lang.Object가 리턴된다. 접근제한자는 def로 선언시 public선언된다. 

5.메서드정의하기


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package _5
 
class SomeClass {
    static void main (args){                        //#1 자동으로 public 접근제한자 없으므로  자바 메인메서드가 된다.
    println "call main"
        def some = new SomeClass()
        some.publicVoidMethod()
        assert 'hi' == some.publicUntypedMethod()//true
        assert 'ho' == some.publicTypedMethod()//true
          combinedMethod()                            //#2 현재 클래스의 정적 메서드 호출
    }
    static void main2 (args){
        args+'before';
        args+'after';;
    }
    static main3 (args){
        args+'before';
        args+'after';;
    }
    void publicVoidMethod(){
    }
    def publicUntypedMethod(){
        return 'hi'   
    }
    String publicTypedMethod() {       
        return 'ho'      
    }
    protected static final void combinedMethod(){
    }   
}
//println '-----';  //이부분 주석을 풀게되면  이클래시는 Script를 상속받게된다 따라서 run메서드를 실행시키게된다.
/* 결과
call main
*/



main 메서드에 흥므로운 것이 있다 
첫째는 : public디폴트이기 때문에 생략 
둘째는 : 실행할 수 있는 클래스의 메인메서드는 인자가 String[] 이어야 한다. 여기서 arg가 암묵적으로Object 가 되는데도 그루비의 메서드 디스패치 덕분에 메인 메서드로 동작한다. 
여기서 void도 생략가능하다 메인 메서드로 동작한다.static main(args) 





6.인자리스트 정의 인자에 따른 메서드 호출.다르게


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package _6
 
class SomeClass {   
    static void main (args){ 
        assert 'untyped' == method(1)                  
        assert 'typed'   == method('whatever')       
        assert 'two args'== method(1,2)
        println  method(1)                
        println  method('whatever')       
        println  method(1,2)              
        println  method(1,2,3)              
    }   
    static method(arg) {       
        return 'untyped'      
    }
    static method(String arg){    
        return 'typed' 
    }
    static method(arg1, Number arg2){    
        return 'two args' 
    }
     def method(arg1,arg2,arg3){    
        return 'boolean args' 
    }
}
/*
결과
untyped
typed
two args
Caught: groovy.lang.MissingMethodException: No signature of method: static _6.SomeClass.method() is applicable for argument types: (java.lang.Integer, java.lang.Integer, java.lang.Integer) values: [1, 2, 3]
Possible solutions: method(java.lang.Object), method(java.lang.String), method(java.lang.Object, java.lang.Number), method(java.lang.Object, java.lang.Object, java.lang.Object)
    at _6.SomeClass.main(_6.groovy:11)
 
*/


오버로딩 같은개념 
여기서 맨마지막 method(1,2,3) 호출이 오류난건 자바에서처럼 메인메서드에서 호출할려는 메서드는 static이여야한다 그걸 위반했다. 

7.고급인자 사용 기술


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package _7
 
import java.util.List;
import java.util.Map;
 
class Summer {
    def sumWithDefaults(a, b, c=0){                         //#1
        return a + b + c
    }
    def sumWithList(List args){                             //#2
        return args.inject(0){sum,i -> sum += i}//0초기값으로주고(누적값sum) list인자만큼 돈다.(인자값:i)
    }   
    def sumWithOptionals(a, b, Object[] optionals){         //#3
        return a + b + sumWithList(optionals.toList())
    }
    def sumNamed(Map args){                                 //#4
        ['a','b','c'].each{args.get(it,0)}      //초기값부여
        return args.a + args.b + args.c
    }
    def sumNamed(Map args,Map args1,Map args2){                                 //#4
        return 3;
    }
}
 
def summer = new Summer()
 
//#1호출
assert 2 == summer.sumWithDefaults(1,1)    
assert 3 == summer.sumWithDefaults(1,1,1)  
println  'summer.sumWithDefaults(1,1)       :'+summer.sumWithDefaults(1,1)     
println  'summer.sumWithDefaults(1,1,1)     :'+summer.sumWithDefaults(1,1,1)
 
//#2호출
assert 2 == summer.sumWithList([1,1])      
assert 3 == summer.sumWithList([1,1,1])    
println 'summer.sumWithList([1,1])      : '+summer.sumWithList([1,1])  
println 'summer.sumWithList([1,1,1])    : '+summer.sumWithList([1,1,1])
 
 
//#3호출
assert 2 == summer.sumWithOptionals(1,1)
assert 3 == summer.sumWithOptionals(1,1,1)
assert 6 == summer.sumWithOptionals(1,1,1,1,1,1)
println    'summer.sumWithOptionals(1,1)          :'+summer.sumWithOptionals(1,1)         
println    'summer.sumWithOptionals(1,1,1)        :'+summer.sumWithOptionals(1,1,1)       
println    'summer.sumWithOptionals(1,1,1,1,1,1)  :'+summer.sumWithOptionals(1,1,1,1,1,1) 
 
 
//map호출
assert 2 == summer.sumNamed(a:1, b:1)      
assert 3 == summer.sumNamed(a:1, b:1, c:1)
assert 1 == summer.sumNamed(c:1)
assert 3 == summer.sumNamed([a:1, b:1, c:1])
assert 3 == summer.sumNamed([a:1, b:1, c:1],[a:1, b:1, c:1],[a:1, b:1, c:1])
println 'summer.sumNamed(a:1, b:1)                                       :'+summer.sumNamed(a:1, b:1)                                       
println 'summer.sumNamed(a:1, b:1, c:1)                                  :'+summer.sumNamed(a:1, b:1, c:1)                                   
println 'summer.sumNamed(c:1)                                            :'+summer.sumNamed(c:1)                                             
println 'summer.sumNamed([a:1, b:1, c:1])                                :'+summer.sumNamed([a:1, b:1, c:1])                                 
println 'summer.sumNamed([a:1, b:1, c:1],[a:1, b:1, c:1],[a:1, b:1, c:1]):'+summer.sumNamed([a:1, b:1, c:1],[a:1, b:1, c:1],[a:1, b:1, c:1]) 
 
/*
결과
summer.sumWithDefaults(1,1)     :2
summer.sumWithDefaults(1,1,1)     :3
summer.sumWithList([1,1])       : 2
summer.sumWithList([1,1,1]) : 3
summer.sumWithOptionals(1,1)          :2
summer.sumWithOptionals(1,1,1)        :3
summer.sumWithOptionals(1,1,1,1,1,1)  :6
summer.sumNamed(a:1, b:1)                                        :2
summer.sumNamed(a:1, b:1, c:1)                                  :3
summer.sumNamed(c:1)                                            :1
summer.sumNamed([a:1, b:1, c:1])                                :3
summer.sumNamed([a:1, b:1, c:1],[a:1, b:1, c:1],[a:1, b:1, c:1]):3
 
*/



여기서 눈여겨볼것이… 3가지있는데. 
1. 디폴트값 
def sumWithDefaults(a, b, c=0){} 
2. 파라미터 집합형자료형 ,로 바로넣기(동적 메시지 디스패처가 넘치는 인자들을 배열에 담아서 전달) 
def sumWithOptionals(a, b, Object[] optional){} 
assert 6 == summer.sumWithOptionals(1,1,1,1,1,1) 
3. 집합자료형 , 바로넣기 2 (동적 메시지 디스패처가 넘치는 인자들을 배열에 담아서 전달) 
def sumNamed(Map args){} 
sumNamed(Map args,Map args1,Map args2){} 
assert 3 == summer.sumNamed(a:1, b:1, c:1) 
assert 3 == summer.sumNamed([a:1, b:1, c:1]) 
assert 3 == summer.sumNamed([a:1, b:1, c:1], [a:1, b:1, c:1], [a:1, b:1, c:1]) 

고급명명기법
1
2
3
4
5
6
class g{
    def g=55;
}
Map args = [ a:1,b:2,c:3,d:4]
println args.'size'();
println new g().'g';

역시 여기서도 스크립트의 파워풀한기능을 볼수있다. 




8.안전한 참조연산자


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package _8
 
def map = [a:[b:[c:1]]]          
 
assert map.a.b.c == 1       //일반적인접근
 
if (map && map.a && map.a.x){           //#1        평가단축기법   
    assert false;
    assert map.a.x.c == null
}   
             
//널포인트 발생에따른 예외처리를 해줘야한다.. 일반적인처리..  a안에는x가 없으니..
try {
    assert map.a.x.c == null
} catch (NullPointerException npe){     //#2
    println "NullPointerException : ${npe}"
}
  
//안전하게 참조하는  ? 연산자를 제공한다 이연산자는 앞에 있는 참조 변수가 null이면 현재 해석중인 표현식을 중지하고 null리턴한다.
assert map?.a?.x?.c == null             //#3
println 'map?.a?.x?.c '+map?.a?.x?.c
/*
결과
NullPointerException : java.lang.NullPointerException: Cannot get property 'c' on null object
map?.a?.x?.c null
*/


괜찮은 기능이기긴 하지만 저 안전한 참조연산자를 쓸정도로 애매모호한 구조를 설계하지말아야 될것같은 느낌이다. 


생성자들


생성자는 세가지 방법으로 호출할수 있다 
1. 자바에서 하던방식 
2. 키워드 as, asType를 이용한 강제형변환 
3. 암묵적 형변환 방식 

9.위치기반 인자를 이용한 생성자


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package _9
 
import java.util.Map;
 
class VendorWithCtor {                     
    String name, product
    VendorWithCtor(){
        println "디폴트 생성자"
    }
     
    VendorWithCtor(Map map){
        println "Map 파라미터1개 생성자호출 : ${map},  ${map.class.name}"
        this.name = name;
    }
    VendorWithCtor(name){
        println "파라미터1개 생성자호출 : ${name},  ${name.class.name}"
        this.name = name;
    }
    VendorWithCtor(name, product) {             //#1
        println "파라미터2개 생성자호출  : ${name}, ${product}"
        this.name    = name
        this.product = product
    }
}
 
def first = new VendorWithCtor('Canoo','ULC_일반적인 생성')   //#2 일반적은 생성자호출
def first2 = new VendorWithCtor()   //#2 일반적은 생성자호출
 
def second = ['Canoo','ULC_강제 형변환'] as VendorWithCtor  //#3 강제 형변환
//def second = ['Canoo','ULC'].asType(VendorWithCtor)  //#3 강제 형변환
def second2 = [] as VendorWithCtor  //#3 강제 형변환
Map map = [a:1,b:2,c:3];
def second3 = map as VendorWithCtor  //#3 강제 형변환 이건안된다..디폴트가탄다.. Map이있지만도..
 
VendorWithCtor third = ['Canoo','ULC_암묵적 형변환']          //#4 암묵적 형변환
VendorWithCtor third2 = ['Canoo 암묵적 형변환']          //#4 암묵적 형변환
 
 
/*
결과
파라미터2개 생성자호출  : Canoo, ULC_일반적인 생성
디폴트 생성자
파라미터1개 생성자호출 : [Canoo, ULC_강제 형변환],  java.util.ArrayList
파라미터1개 생성자호출 : [],  java.util.ArrayList
디폴트 생성자
파라미터2개 생성자호출  : Canoo, ULC_암묵적 형변환
파라미터1개 생성자호출 : Canoo 암묵적 형변환,  java.lang.String
*/


역시 자바와다르게 형변환할때도 생성자 호출이된다… 하지만 저 해쉬맵은 왜안될까… 
역시 자바와다르게 스크립트기반언어기 때문에 개발시 부분부분 테스트가 더욱더 세밀하게 이루워져야할것같다. 




이름기반 이자로 생성자 호출하기


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package _10
 
class Vendor {                     
    //String name, product
    def name, product
    //private String name, product  //private 해도 적용됨
}
 
new Vendor()                           
new Vendor(name:   'Canoo')             
new Vendor(product:'ULC')               
new Vendor(name:   'Canoo', product:'ULC')            
new Vendor(name:   'Canoo', product:1)         //자동케스팅   
//new Vendor(name:   'Canoo', product2:1) //오류남            
 
def vendor = new Vendor(name: 'Canoo')
assert 'Canoo' == vendor.name
println vendor.name;
 
 
//암묵적 생성
java.awt.Dimension area;
area = [20,100];
assert area.width ==20;
assert area.height == 100;
println    'area.width :    '+area.width ;  
println    'area.height : '+area.height ;
 
/*
결과
Canoo
area.width :    20.0
area.height : 100.0
 
*/


위처럼 사용할수도있다.. 하지만 위 같은 상황을 쓸일이 많이 생길지 의문이다.. 

클래스와 스크립트 구성하기



그루비 클래스 생성을 할수 있다. 

1. 그루비 파일에 클래스 정의가 ‘하나도 없다면’ 그파일은 스크립트로 동작한다. 
다시말해 자동으로 Script 클래스를 상속받는다. 
자동으로 생성된 클래스의 이름은 확장자를 뺀 소스 파일의 이름과 같다. 
파일의 코드는run메서드에 들어가고, 실행할 수 있겠끔 main 메서드도 만들어진다. 
2. 그루비 파일에 클래스가 ‘하나’ 만 있고, 그 클래스 이름이 확장자를 제외한 소스 파일의 이름과 같다면 자바와 같은 일대일 대응 관계가 된다. 
3. 그루비는 그루비 파일에 클래스가 ‘몇개’ 정의되어있든 그것들이 public이든 private이든 소스파일의 이름과 같은 클래스가 있던 없던 다수용할수 있다. 그루비 컴파일러인 groovyc는 파일에 정의된 클래스 각각에 대해 *.class 파일을 생성한다. 이파일을 프롬프트에서 goovy를 통해서 실행하거나 IDE에서 호출하는 식으로 스크립트로서 사용할 때는 파일에서 첫번째로 정의된 클래스에 main 메서드가 있어야한다. 
4. 그루비 파일에 클래스 정의와 스크립트 코드를 섞어서 쓸수도 있다 이렇게 하면 실행시에 주 클래스는 스크립트 코드가 된다 따로 소스 파일이 이름과 같은 이름으로 클래스를 만들 필요는 없다. 

명시적으로 컴파일 하지 않았다면 그루비에서 클래스를 찾을 때는 클래스 이름 과 같은 *.groovy소스 파일을 검색한다. 이때 이름이 중요하다 그루비는 찾고 있는 클래스와 이름이 같은 파일만 검색한다 , 파일을 발견하면 그파일 안에 모든 클래스를 분석해서 로딩한다. 



패키지로 구조화하기


*.groovy소스 파일들은 *.class파일로 컴파일하지 않아도 쓸수있기때문에 클래스를 찾을때 *.groovy 파일도 함께 찾는다. 이때도 같은 방식으로 검색한다 즉 그루비는 business/Vendor.groovy파일에서 business 패키지에 속한 Vendor 클래스를 검색할것이다. 

클래스패스


그루비가 *.groovy 파일을 찾을때 클래스 패스를 사용한다. 
주어진 클래스를 찾다가 *.class 와 *.groovy 모두 발견됐다면 둘중 최근에 변경된 파일을 사용한다 즉 *.groovy 가 *.class보다 최근에 변경됐다면 *.groovy 컴파일한후 *.class를 사용한다. 

패키지예제 

1
2
3
4
5
6
7
8
9
10
11
12
package business
 
class Vendor {
    public String  name
    public String  product
    public Address address = new Address()
}
 
class Address  {
    public String  street, town, state
    public int     zip
}


임포트예제 

1
2
3
4
5
6
7
import business.*
 
def canoo = new Vendor()
canoo.name          = 'Canoo Engineering AG'
canoo.product       = 'UltraLightClient (ULC)'
 
assert canoo.dump() =~ /ULC/


몇몇 스크립트 언어와 달리 임포트를 해도 클래스나 파일을 실제로 포함하는것이 아니다 다만 클래스 이름을 해석할 때 참고할 정보를 주는것뿐이다. 

그루비는 패키지 6개와 클래스2개를 자동 임포트한다. 
java.lang.* 
java.util.* 
java.io.* 
java.net.* 
groovy.lang.* 
groovy.util.* 
java.math.BigInteger 
java.mat.BigDecimal 


14.클래스 별칭


as 를통하여 클래스 별칭(type aliasing)을 만들수 있다. 
이것은 클래스 이름 충돌을 해결하거나 지역적인 수정 혹은 써드파트 라이브러리 버그를 수정하는데 사용한다. 

1
2
3
4
5
6
7
package _14.oldpack
 
public class _14_A {
    public def calc(def a, def b){
        return a+b;
    }
}


1
2
3
4
5
6
7
8
9
10
11
package _14
 
import _14.oldpack._14_A as GO;
 
class A_14_A extends GO {
    public def calc(def a, def b){
        return a*b;
    }
}
def  f =  new A_14_A();
println f.calc(4,4);




클래스 패스에 관련된 추가 사항


그루비가 *.class 파일과 *.groovy 파일에서 클래스를 찾아낸다는 점은 그루비를 다를때 이해하고 있어야 하는 중요한 부분이다. 안타깝게도 여기서 문제가 종종 발생하는데... 

그루비의 클래스패스는 %GROOVY_HOME%\conf 디렉터리의 특별한 설정파일이있다. 
groovy-starter.conf 

맨마지막줄 #지워서 활성화시키면 좋은 기능이 살아난다. 
user.home으로 상징되는 사용자의 홈 디렉터리에서 서브디렉터리로 .groovy/lib를 만들고 *.class나 *.jar 파일 넣어두면 그루비가 쓸때마다 로딩되도록한다. 
user.home찾기힘들다면 
groovy –e “println System.properties.’user.home’” 

셀제목셀제목셀제목
구분정의목적과 사용법
JDK/JRE%JAVA_HOME%/lib,%JAVA_HOME%/lib/extJRE부트 클래스패스와 확장 라이브러리들
OS설정CLASSPATH변수일반적인 기본설정
커맨드라인CLASSPATH 변수특수설정
java-cp,--classpath,option실행시 설정
Groovy%GROOVY_HOME%/lib그루비 실행환경
Groovy-cp그루비 실행시 설정
Groovy.현재 디렉터리 클래스패스로 하는 디폴트 클래스 패스


고급 객체지향 기능


1. 상속하기 
그루비에서는 그루비와 자바의 클래스나 인터페이스를 상속받아서 확장할 수 있다. 자바 쪽에서도 그루비 클래스나 인터페이스를 상속받을수 있다. 
2. 인터페이스 
자바의 인터페이스를 완벽하게 지원한다. 
자바의 추상 메서드도 지원한다. 
그루비는 더 동적으로 인터페이스를 사용할 수 있는 기능을 제공한다. 메서드가 하나만 있는 인터페이스인 MyInterface와 클로저 myClosure가 있다면 이 myClosure를 키워드 as 이용하여 MyInterface로 강제 형변환할수도있다 

16.멀티메서드


자바에 메서드를 호출하면 명시한 자료형을 참조해서 메서드를 찾는다 그에 반해 그루비에서는 인자의 동적 자료형을 고려해서 적절한 메서드를 찾아낸다 그루비의 이런 기능을 멀티메서드(multimethod)라고한다 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package _16
 
def oracle(Object o) { return 'object' }
def oracle(String o) { return 'string' }
 
Object x = 1    
Object y = 'foo'
 
assert 'object' == oracle(x)
assert 'string' == oracle(y) //#1 자바라면 ‘object’를 호출할것이다.
println oracle(x);
println oracle(y);
println oracle(y as Object);
/* 결과
object
string
object
*/


인자x는 Object로 표시됐지만 동적으로는 Integer이다. 
인자y는 Object로 표시됐지만 동적으로는 String이다. 
명시적으로 가도록 선언하고 싶다면 명시적으로 형변환을 해주면된다. 
그루비에서는 인자의 자료형을 동적으로 검사한다. 

17.equals를 선택적으로 재정의하는 메서드


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package _17
 
class Equalizer {
    boolean equals(Equalizer e){ //이건 equals의 오버라이딩 된것이 아니라.  오버로딩된것이다.
        println 'equals Equalizer : '+e.class
        return true
    }
//  boolean equals(e){ //이건 equals의 오버라이딩 된것이 아니라.  오버로딩된것이다.
//      println 'equals Object : '+e.class
//      return true
//  }
}
 
Object same  = new Equalizer()         
Object other = new Object()
//여기 객체를 Object로 받았다.
 
assert   new Equalizer().equals( same  ) //Equalizer에서 정의한 equals로 간다. //그루비는 자동으로 클레스형대로 찾아서간다
assert ! new Equalizer().equals( other ) // Object의 equals 메서드를 호출한다. //그루비는 자동으로 클레스형대로 찾아서간다
/*결과
equals Equalizer : class _17.Equalizer
*/



뭐가 다른지 자바를 보자 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import GG;
import java.util.Date;
 
 
class GG{
     
    public boolean equals(String obj) {
        System.out.println("equals String");
        return true;
    }
}
 
public class TestJava {
    public static void main(String[] args) {
        GG g = new GG();
         
        Object s = new String("---");
        Object d = new Date(0);
         
        System.out.println( g.equals(s) );
        System.out.println( g.equals(d) );
         
         
        System.out.println( g.equals((String)s) );
    }
}
/*결과
false
false
equals String
true
 */





18.그루비빈 사용하기


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package _18
 
import java.io.Serializable
 
class MyBean implements Serializable {
    def untyped
    String typed
    def item1, item2
    def assigned = 'default value'
    //접근제한자가 붙은건 자동프로퍼티가 생기지 않는다.
}
 
def bean = new MyBean()
assert 'default value' == bean.getAssigned()
println bean.getAssigned();
 
bean.setUntyped('some value')
assert 'some value' == bean.getUntyped()
println bean.getUntyped();
 
bean = new MyBean(typed:'another value',untyped:'untyped---',item1:'item1---')
assert 'another value' == bean.getTyped()
assert 'untyped---' == bean.getUntyped()
assert 'item1---' == bean.getItem1()
println bean.getTyped();
println bean.getUntyped();
println bean.getItem1();
 
/*
결과
default value
some value
another value
untyped---
item1---
*/


여기서 중요하게 볼것이 생성할때 프로퍼티를 통하여 초기값을 줄수 있다는것이다. 


읽기전용 프로퍼티
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package _18
import java.io.Serializable
 
class MyBean implements Serializable {
    final def untyped='untyped'
}
 
def bean = new MyBean()
assert 'untyped' == bean.getUntyped()
println bean['untyped']
bean['untyped']='tttttttttt'; //error
bean.setUntyped('----------untyped');//error
 
//프로퍼티 변수에 final을 붙이면 읽기전용이 된다




프로퍼티 name에 접근하는 방법 비교 

자바그루비
getName()name
setName('a')name='a'

19.getter, setter 메서드 만들기만해도 그루비 프로퍼티 접근형식으로 접근가능


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package _19
 
class MrBean {
    String firstname, lastname              //#1
     
    String getName(){                       //#2 가상 프로퍼티의 생성자 가상의name이생긴거다/
        return "$firstname $lastname"
    }
}
 
def bean = new MrBean(firstname: 'Rowan')   //#3
bean.lastname = 'Atkinson'                  //#4
 
assert 'Rowan Atkinson' == bean.name        //#5 자동으로 생긴name 읽고 쓰고한다.
println bean.name ;
/*결과
Rowan Atkinson
*/


위에서 보는것처럼 프로퍼티접근하는거에대한 일관성이 유지된다. 오우.~ 




20.프로퍼티 함수를 통하여 접근하지 않고! @기호로 필드 변수에 바로 접근하기.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package _20
 
class DoublerBean {
    public value                           //#1
     
    void setValue(value){
        this.value = value                 //#2
    }
     
    def getValue(){                       
        value * 2                          //#3
    }
}
 
def bean = new DoublerBean(value: 100)    
 
assert 200 == bean.value                   //#4
assert 100 == bean.@value                  //#5
 
println 'bean.value;    :  '+bean.value;
println 'bean.@value;   :  '+bean.@value;
/* 결과
bean.value;    :  200
bean.@value;   :  100
*/


필드 변수와 같은 영역에서는 fieldname 이나this.fieldname을 필드 변수에 대한 접근으로 해석하며 프로퍼티에 대한 접근으로 보지 안흔ㄴ다 영역 밖에서는 referenc.@fieldname문법을 이용해야 동일한 효과를 얻는다. 
주의할것이 하나있는데 정적인 영역 (static context)에서 @를 사용하거나 def x = this;x.@fieldname 같은식으로 사용하면 이상한 현상이 발생한다. 권장하지 않는다. 


자바빈 스타일 이벤트처리


그루비는 빈의 내부를 검사(bean introspection) 해서 할당문의 필드 변수가 리스너 등록 메서드 인지 확인한다 등록 메서드로 판단되면 ClosureListener 클래스가 자동으로 붙고 이벤트가 발생하면 클로저를 호출해준다. 

그루비 

1
2
3
4
def button = new JButton('push');
button.actionPerformed ={event->
    println button.text;
}



자바 

1
2
3
4
5
6
final JButton button = new JButton("push");
button.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        System.out.println(button.getText());
    }
});





필드, 접근자, 맵, Expando


그루비 코드에는 object.name같은 표현이 자주 눈에 띈다 그루비가 이런 표현식을 만났을 때 하는 일을 나열해 보면 다음과같다. 

1. object가맵이면 object.name은 맵의 키에서 name을 찾아 대응하는 값을 가르킨다. 

1
2
def map = [a:1,b:2];
println map.a;



2. name이 object의 프로퍼티일 때는 프로퍼티를 찾아서 가리킨다 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class gogo{
    def oneProperty='oneProperty_value'
    def towProperty='towProperty_value'
    public def getOneProperty() {
        println 'call getOneProperty'
        return oneProperty;
    }
}
def g = new gogo();
println g.oneProperty;
/*결과
call getOneProperty
oneProperty_value
*/



3. 모든 그루비 객체는 자신만의 getProperty(name) 과 setProperty(name,value)메서드를 정의할수 있다. 맵(map)객체도 이방법으로 키를 프로퍼티처럼 제공한다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
class gogo{
    def oneProperty='oneProperty_value'
    def towProperty='towProperty_value'
    public def getOneProperty() {
        println 'call getOneProperty'
        return oneProperty;
    }
}
def g = new gogo();
println g.getProperty('towProperty');
/*결과
towProperty_value
*/




4. object.get(name)메서드를 만들면 필드 변수 접근을 가로챌수 있다 이부분은 그루비 실행환경의 최전방에 속한다 자바빈에 적당한 프로퍼티도 없고 getProperty메서드도 구현되지 않았을때 사용된다. 
그리고 당연히 name에 특수 문자가 있어서 식별자(identifier)로 사용할수 없을 때도 문자열로 만들면 사용할수 있다 즉 object.’my-name’과같이 만들면된다 또 GString 이용할수도있다. def name = ‘my-name’;Object.”$name” 처럼 써도된다 Object에는 getAt이라는 메서드가 있어서 object[name] 형식으로 프로퍼티에 접근하도록 위임할수 있다. 

21.프러퍼티 접근 및 get,set메서드 활용 예제


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package _21
 
class Go{
    def g;
    public void setG(def g) {
        println 'setG '+g;
        this.g = g;
    }
     
     
    public void set(def name,def value) {
        println 'No such property Name -> Set Name:'+name+', value:'+value;
    }
    public def get(String name) {
        return 'No such property Name -> '+name;
    }
     
}
  
def g = new Go([g:'1']);
println 'g.g;  : ' + g.g; 
println 'g.gg; : ' + g.gg;
 
println '';
g.g='g_value'
g.gg='gg_value';
println '';
 
println 'g.g;  : ' + g.getAt("g"); 
println 'g.gg; : ' + g.getAt("gg");
 
 
 
/*결과
setG 1
g.g;  : 1
g.gg; : No such property Name -> gg
 
setG g_value
No such property Name -> Set Name:gg, value:gg_value
 
g.g;  : g_value
g.gg; : No such property Name -> gg
*/



여기서 주의할것은 get,set 메서드는 프로퍼티가 없을경우에만 탄다는것이다. 
프로퍼티가 있는것은 자신의 접근 메서드로 바로 호출된다.
 



그루비 특장점 활용하자


그루비의 강력한 세가지 기능을 설명한다 
1. GPath 
2. 확산연산자(spread operator 
3. use 


22.GPath는 객체들의 구조를 탐색하는 강력한 도구이다. XPath이름을 따서 왔다나..?


GPath예제 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package _22
 
//현재 객체를 문자열로 표현한것이다
println 'this : '+this;
 
//이객체의 클래스가 무엇인지 알려면 this.getClass라고하면되지만 그루비에서는 다음처럼 가능하다.
println 'this.class   : '+this.class;
 
//클래스 객체는 getMethods메서드를 통해서 메서드들의 리스트를 제공한다
println 'this.class.methods   :  '+this.class.methods;
 
//모든 메서드에는 getName메서드가있으니 이렇게 입력해보자
println 'this.class.methods.name   : '+this.class.methods.name;
 
//get으로 시작하는 메서드이름가져와보자
println 'this.class.methods.name.grep(~/get.*/) : '+this.class.methods.name.grep(~/get.*/);
 
/*결과
this : _22._22@1766806
this.class   : class _22._22
this.class.methods   :  [public void _22._22.super$1$wait(), public void _22._22.super$1$wait(long,int), public void _22._22.super$1$wait(long), public java.lang.String _22._22.super$1$toString(), public void _22._22.super$1$notify(), public void _22._22.super$1$notifyAll(), public java.lang.Class _22._22.super$1$getClass(), public boolean _22._22.super$1$equals(java.lang.Object), public java.lang.Object _22._22.super$1$clone(), public int _22._22.super$1$hashCode(), public void _22._22.super$1$finalize(), public java.lang.Object _22._22.this$dist$invoke$4(java.lang.String,java.lang.Object), public void _22._22.this$dist$set$4(java.lang.String,java.lang.Object), public java.lang.Object _22._22.this$dist$get$4(java.lang.String), public java.lang.Object _22._22.super$3$getProperty(java.lang.String), public void _22._22.super$3$setProperty(java.lang.String,java.lang.Object), public void _22._22.super$3$println(), public void _22._22.super$3$println(java.lang.Object), public void _22._22.super$3$print(java.lang.Object), public void _22._22.super$3$printf(java.lang.String,java.lang.Object), public void _22._22.super$3$printf(java.lang.String,java.lang.Object[]), public java.lang.Object _22._22.super$3$evaluate(java.lang.String), public java.lang.Object _22._22.super$3$evaluate(java.io.File), public groovy.lang.MetaClass _22._22.super$2$getMetaClass(), public void _22._22.super$2$setMetaClass(groovy.lang.MetaClass), public groovy.lang.Binding _22._22.super$3$getBinding(), public void _22._22.super$3$setBinding(groovy.lang.Binding), public void _22._22.super$3$run(java.io.File,java.lang.String[]), public java.lang.Object _22._22.super$3$invokeMethod(java.lang.String,java.lang.Object), public static void _22._22.main(java.lang.String[]), public java.lang.Object _22._22.run(), public void groovy.lang.Script.setBinding(groovy.lang.Binding), public java.lang.Object groovy.lang.Script.invokeMethod(java.lang.String,java.lang.Object), public groovy.lang.Binding groovy.lang.Script.getBinding(), public void groovy.lang.Script.println(), public void groovy.lang.Script.println(java.lang.Object), public void groovy.lang.Script.run(java.io.File,java.lang.String[]) throws org.codehaus.groovy.control.CompilationFailedException,java.io.IOException, public void groovy.lang.Script.setProperty(java.lang.String,java.lang.Object), public java.lang.Object groovy.lang.Script.getProperty(java.lang.String), public void groovy.lang.Script.print(java.lang.Object), public void groovy.lang.Script.printf(java.lang.String,java.lang.Object), public void groovy.lang.Script.printf(java.lang.String,java.lang.Object[]), public java.lang.Object groovy.lang.Script.evaluate(java.io.File) throws org.codehaus.groovy.control.CompilationFailedException,java.io.IOException, public java.lang.Object groovy.lang.Script.evaluate(java.lang.String) throws org.codehaus.groovy.control.CompilationFailedException, public groovy.lang.MetaClass groovy.lang.GroovyObjectSupport.getMetaClass(), public void groovy.lang.GroovyObjectSupport.setMetaClass(groovy.lang.MetaClass), public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException, public final void java.lang.Object.wait() throws java.lang.InterruptedException, public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException, public boolean java.lang.Object.equals(java.lang.Object), public java.lang.String java.lang.Object.toString(), public native int java.lang.Object.hashCode(), public final native java.lang.Class java.lang.Object.getClass(), public final native void java.lang.Object.notify(), public final native void java.lang.Object.notifyAll()]
this.class.methods.name   : [super$1$wait, super$1$wait, super$1$wait, super$1$toString, super$1$notify, super$1$notifyAll, super$1$getClass, super$1$equals, super$1$clone, super$1$hashCode, super$1$finalize, this$dist$invoke$4, this$dist$set$4, this$dist$get$4, super$3$getProperty, super$3$setProperty, super$3$println, super$3$println, super$3$print, super$3$printf, super$3$printf, super$3$evaluate, super$3$evaluate, super$2$getMetaClass, super$2$setMetaClass, super$3$getBinding, super$3$setBinding, super$3$run, super$3$invokeMethod, main, run, setBinding, invokeMethod, getBinding, println, println, run, setProperty, getProperty, print, printf, printf, evaluate, evaluate, getMetaClass, setMetaClass, wait, wait, wait, equals, toString, hashCode, getClass, notify, notifyAll]
this.class.methods.name.grep(~/get./) : [getBinding, getProperty, getMetaClass, getClass]
*/




23.확산 연산자 사용하기


이연산자는 확산-도트 연산자에서 리스트의 각 요소로 대응해 주는 역활을 한다. 
배열기호 연산자와는 반대라고 할수 있다 배열기호 연산자는 쉼표로 구분된 객체들을 모아서 리스트로 만들어준다. 확산 연산자는 리스트의 요소들을 수신자에게 배포해준다. 수신자는 여러 인자를 받을수 있는 메서드이거나 리스트를 생성할수있다. 
어떤 메서드가 결과 값을 리스트에 담아서 리턴 하는데 호출한 측에서 이결과 값들을 다른 메서드에 전달해야 하는 상황을 생각해보자 확산 연산자를 이용하면 결과 값을 그대로 두번재 메서드에 인자로 전달할수있다. 
아래와 같이 하면 여러개 리턴하는 메서드와 이를 수신하는 메서드들을 섞어 쓰기쉽고 수신하는 메서드에서는 인자로 따로 선언할수있다. 

23.확산연산자 예제.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package _23
 
def getList(){
    return [1,2,3,4]
}
 
def sum(a,b,c,d){
    return a+b+c+d;
}
println 'this.list;           : ' + this.list;
println 'getProperty ('list') : ' + getProperty ('list');
 
println 'sum(*list)  : '+  sum(*list)
println 'sum(list)   : '+  sum(list)
println '-------------'
println '[*list]       : ' + [*list];   //여기서 확산연산자의 기능을 확인해볼수있다. 오리지널 값이 그대로 .값이들어간다.
println '[]+list       : '+([]+list);   //뒤쪽에+연산이 들어가는건  list를 처리한 값을 앞쪽 리스트에 넣겠다는소리다. 즉 확산연산자랑 같은 일을한다.
println '[list]       : ' + [list];     //그자체가들어간다/
assert 10==sum(*list);
assert 10==sum(list);
assert 11==sum([2,2,3,4]);
assert 12==sum(3,2,3,4);
 
 
println '-------------'
def range=(1..3);
assert [0,1,2,3] ==[0,*range];
assert [0,1,2,3] ==[0]+range;
assert [0,1,2,3] ==[0,range].flatten();
println '[0,*range];            :  '+([0,*range]);        //확산연산자의 연산된값 즉 오리지널
println '[0,range]              :  '+[0,range];
println '[0]+range;             :  '+([0]+range);            
println '[0,range].flatten();   :  '+([0,range].flatten());  
 
 
 
println '-------------'
//맵에서도 쓸수있다.
def map =[a:1,b:2];
assert [a:1,b:2,c:3] == [c:3,*:map];
assert [a:1,b:2,c:3] == [c:3,*:map];
assert [a:1,b:2,c:3] == [c:3]+[*:map];
 
println '[c:3,*:map];   : '+([c:3,*:map]);  
println '[c:3,*:map];   : '+([c:3,*:map]);  
println '[c:3]+[*:map]; : '+([c:3]+[*:map]);
 
/*결과
this.list;           : [1, 2, 3, 4]
getProperty ('list') : [1, 2, 3, 4]
sum(*list)  : 10
sum(list)   : 10
-------------
[*list]       : [1, 2, 3, 4]
[]+list       : [1, 2, 3, 4]
[list]       : [[1, 2, 3, 4]]
-------------
[0,*range];            :  [0, 1, 2, 3]
[0,range]              :  [0, 1..3]
[0]+range;             :  [0, 1, 2, 3]
[0,range].flatten();   :  [0, 1, 2, 3]
-------------
[c:3,*:map];   : [c:3, a:1, b:2]
[c:3,*:map];   : [c:3, a:1, b:2]
[c:3]+[*:map]; : [c:3, a:1, b:2]
 
*/


여기를 보면 확산연산자의 진짜능력을 알수있다. 
[*list] : [1, 2, 3, 4] 
[]+list : [1, 2, 3, 4] 
[list] : [[1, 2, 3, 4]] 


24.키워드 use를 이용한 카테고리 섞기



'숫자형 문자열' + '숫자형 문자열' = 산술계산값 
처럼 결과값을 얻고 싶다고 한다면 ‘1’+’1’ 하면 안될것이다. 
이문제를 해결하기위해 그루비는 use키워드를 제공한다 이키워드는 카테고리를 이용해 
클래스에 인스턴스 메서드를 추가할 수 있다. 

1
2
3
use (StringCalculationCategory) {
    write(read()+read())
}


여기서 카테고리는 정적 메서드 (카테고리 메서드) 가 포함된 클래스다 
키워드 use는이 메서드의 첫째 인자로 문자열의 인스턴스를 전달해서, 인스턴스 메서드처럼 동작하게 해준다. 

1
2
3
4
5
class StringCalculationCategory {   
    static def plus(String self, String operand) {
        //구현
    }        
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package _24
 
class StringCalculationCategory {   
    static def plus(String self, String operand) {
        println 'plus String'
        try {   
            return self.toInteger() + operand.toInteger()
        }
        catch (NumberFormatException fallback){
            return (self << operand).toString()
        }
    }
     
    static def plus(def self, def operand) {
        println 'plus Def'
            return (self + operand)
    }
     
    static def plus(ArrayList self, ArrayList operand) {
        println 'plus ArrayList'
        //(self + operand) 이렇게하게되면 재귀호출이된다.  오류남
        //self.plus(operand) 이렇게하게되면 재귀호출이된다.  오류남
            int cnt = self.size;
            for (int i = 0; i < operand.size; i++) {
                self[cnt++] = operand[i]
            }
            return self;
    }
     
              
    static def minus(String self, String operand) {
        println 'minus String'
        try {   
            return self.toInteger() - operand.toInteger()
        }
        catch (NumberFormatException fallback){
            return (self << operand).toString()
        }
    }       
     
 
//여기서부터 StringCalculationCategory 클래스의 정의를 따르겠다.
use (StringCalculationCategory.class) {
    assert 1    == '1' + '0'
    assert 2    == '1' + '1'
    assert 'x1' == 'x' + '1'
    assert 0    == '1' - '1'
    println '------------------';
    println "'1' + '0'   :  ${('1' + '0') } "
    println "'1' + '1'   :  ${('1' + '1') } "
    println "'x' + '1'   :  ${('x' + '1') } "
    println "'1' - '1'   :  ${('1' - '1') } "
     
    println ([1,2,3,4]+[5,6,7,8]);
     
}
 
/*결과
plus String
plus String
plus String
minus String
------------------
plus String
'1' + '0'   :  1
plus String
'1' + '1'   :  2
plus String
'x' + '1'   :  x1
minus String
'1' - '1'   :  0
plus ArrayList
[1, 2, 3, 4, 5, 6, 7, 8]
 
*/



카테고리는 주어진 클로저와 현재 스레드에서만 동작한다 이런식의 변경이 전역적으로 반영되면 부작용이 발생되기때문이다. 
1. 특수목적의 메서드를 제공한다 StringCalculation Category에서 본것처럼 연산 메서드의 대상이 동일한 클래스이고 기존 동작을 재정의해야 할 때 사용한다 예제처럼 연산자를 재정의하는 특수한 경우에도 쓸수있다. 
2. 라이브러리 클래스에 메서드를 추가한다 ‘불안전 라이브러리 클래스’가 의심될때 사용하면 효과적이다. 
3. 함께 동작하는 서로 다른 수신자 클래스를 위한 메서드 모음을 제공한다. 예를들어 java.io.OutputStream을 위한 encryptedWrite 메서드와 java.io.InputStream을 위한 encryptedWrite 메서드와 java.io.InputStream을 위한 decryptedRead메서드를 제공할수 있다. 
3. 자바에서 Decorator 패턴을 써야 하는 겨웅에는 사용한다 하지만 메서드들을 많이 만들어야 하는 불편이 더는 겪지 않아도 된다. 
4. 클래스가 너무 커졌을 때 이를 한개의 핵심 클래스와 상황에 맞는 여러개의 카테고리로 나눌수 있다 그리고 use에는 카테고리 클래스를 여러개줄수 있으므로 이카테고리들을 핵심 클래스와 함께 사용한다. 

25.use 메서드 사용하기


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package _25
 
class PCategory{
    static void gogoSave(Object self){
        //self 지정하는함수
        println 'gogoSave Call '+self.gogo;;
    }
}
class GG{
 
    def gogo=[1,3]
    GG(){  
        use (PCategory){
            gogoSave();
        }
        gogoSave();
    }
    public void gogoSave(){
        println 'GG Class gogoSave()';
    }
     
     
}
 
def gg = new GG();
 
/*결과
gogoSave Call [1, 3]
GG Class gogoSave()
*/


카테고리 메서드를 Object에 할당하면 그 메서드는 모든 객체에서 사용할수 있다. 
Object보다 더작은 영역에 적용하는 법에도 관심이 있을것이다 모든Collection 클래스나 여러분이 작성한 클래스들 중 특정 인터페이스를 공유하는 비즈니스 객체들 전부에 대해 적용할수도있다. 
use에는 카테고리 클래스 여러개를 줄수 있다 카테고리들은 쉼표로 불리하거나 리스트로줘도된다. 
use(a,b,c,d) use([a,b,c,d]).. 


그루비 메타 프로그래밍




그루비에는 가로챌수 있는 지점이 셀수 없이 많다 
그중 어떤것을 고르는지에 따라 그루비가 내부적으로 제공하는 기능을 이용할수도 있고 
재정의할 코드의 양이 달라지기도 한다. 
특징들이 모여서 그루비의 메타오브젝트 프로토콜(MOP,Meta-Object Protocol)를 이룬다 

메타 프로그래밍 이해



그루비의 모든것은 GroovyObject 인터페이스에서 시작된다. 

1
2
3
4
5
6
7
8
9
package groovy.lang;
 
public interface GroovyObject {
Object invokeMethod(String name, Object args);
Object getProperty(String propertyName);
void setProperty(String propertyName, Object newValue);
MetaClass getMetaClass();
void setMetaClass(MetaClass metaClass);
}


그루비에서 당신이 작성한 모든 클래스는 GroovyClassGenerrator에 의해서 생성된다 덕분에 
그클래스들은 GroovyObject 인터페이스를 구현하여 각메서드의 디폴트 구현을 포함하게된다. 
(재정의하지 않았다면말이다.) 

보통의 자바 클래스를 그루비 클래스로 인식시키려면 GroovyObject인터페이스를 구현하면된다 더간편하게는 디폴트 메서드를 구현되어있는 GroovyObjectSupport추상클래스를 상속해도된다. 




MetaClass
GroovyObject는 메타 오브젝트 프로토콜의 핵심인 MetaClass와 밀접한 관련이 있다. 이클래스가 Groovy 클래스에 대한 모든 메터 정보를 제공한다 여기서 제공되는 메타정보들은 사용가능한 메서드,필드, 변수, 프로퍼티등이다. 그리고 다음 메서들들도 구현되어있다. 
1
2
3
4
Object invokeMethod(Object obj,String methodName,Object args)
Object invokeMethod(Object obj,String methodName,Object[] args)
Object invokeStaticMethod(Object obj,String methodName,Object args)
Object invokeStaticMethod(Object obj,String methodName,Object[] args)

위메서드들이 실제로 메서드 호출을 담당하는 메서드들이디. 이메서드들은 자바의 리플렉션 API나 자동으로 생성된 리플렉터(reflector) 클래스를 통해서 메서드를 호출한다 성능을 생각한다면 기본적으로는 리플렉터 메서드를 이용한다. 




Groovy-Object.invokeMethod의 디폴트 구현은 모든 호출을 자신의 MetaClass에게 전달한다. MetaClass는 MetaClassRegistry라는 중앙 저장소에 저장되고 꺼내진다. 





1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package groovy.lang;
//디폴트 메서드를 구현되어있는 GroovyObjectSupport
import org.codehaus.groovy.runtime.InvokerHelper;
public abstract class GroovyObjectSupport implements GroovyObject {
 
    // never persist the MetaClass
    private transient MetaClass metaClass;
 
    public GroovyObjectSupport() {
        this.metaClass = InvokerHelper.getMetaClass(this.getClass());
    }
 
    public Object getProperty(String property) {
        return getMetaClass().getProperty(this, property);
    }
 
    public void setProperty(String property, Object newValue) {
        getMetaClass().setProperty(this, property, newValue);
    }
 
    public Object invokeMethod(String name, Object args) {
        return getMetaClass().invokeMethod(this, name, args);
    }
 
    public MetaClass getMetaClass() {
        if (metaClass == null) {
            metaClass = InvokerHelper.getMetaClass(getClass());
        }
        return metaClass;
    }
    public void setMetaClass(MetaClass metaClass) {
        this.metaClass = metaClass;
    }
}


MetaClassRegistry는 싱글톤 이어야하지만 아직은 아니다. 어쨌든 실질적으로 한개뿐인 이객체의 인스턴스를 얻을떄는 InvokerHelper의 팩토리 메서드를 사용한다. 



위그림 Groovy in Action책 발췌하였습다. 


출처 - http://www.gliderwiki.org/wiki/215

'Language > Groovy' 카테고리의 다른 글

DSL(Domain Specific Language) 이해하기  (0) 2016.09.09
Metaprogramming  (0) 2016.09.09
그루비의 동적 객체지향 - 2  (0) 2016.09.08
What does .delegate mean in groovy?  (0) 2016.09.08
ExpandoMetaClass  (0) 2016.09.08
: