낙서장
Object creation with Groovy! 본문
Groovy를 이용한 객체 생성 및 초기화에 대해 알아보자.
import groovy.transform.ToString
class Blog {
@ToString(includeNames = true)
static class Article {
String category
Integer numberOfViewer
}
static void main(String[] args) {
// method 1
def article = new Article()
article.category = "none"
article.numberOfViewer = 0
// method 2
article = article.with {
category = "development"
numberOfViewer = 1
it
}
// method 3
article.tap {
category = "life"
numberOfViewer = 2
}
}
}
다음과 같은 예제 코드가 있다고 가정해보자. 안타깝게도 블로그에서 지원하는 Syntax highlighting에 그루비가 없다(...). 갑자기 상대적 박탈감이 느껴지지만 중요하지 않으니 넘어간다.
위 코드에서 짚고 넘어갈 부분이 몇 가지 있지만..
* @ToString은 해당 어노테이션이 붙어있는 클래스의 toString() 메소드를 생성해주는 역할을 한다. 물론 스스로 toString() 메소드를 만들고 싶은 경우 toString()을 override 해서 구현할 수 있다. 하지만 이러한 language supported annotation을 사용할 때는 언어의 스펙에 따라 언제든지 바뀔 수 있음을 인지하고 사용해야 한다.
* Groovy는 변수 혹은 함수 선언 시 타입을 구체적으로 명시하지 않고 def를 사용할 수 있다.
* Groovy의 함수는 명시적으로 return 예약어를 사용하지 않는 경우 가장 마지막 statement의 결과값을 리턴한다.
아무튼 변수 생성 및 초기화 과정에서는 세 가지 방법을 사용할 수 있다.
def article = new Article()
article.category = "none"
article.numberOfViewer = 0
가장 기본적은 방법으로, 객체 생성 후 모든 dot accessor 를 이용해서 모든 멤버 변수에 값을 넣어주는 방법이 있다. 전통적이고 직관적인 방법이다.
article = article.with {
category = "development"
numberOfViewer = 1
it
}
다음은 groovy with을 이용한 방법이다. with method에 대해서는 다음과 같이 설명하고 있다.
Allows the closure to be called for the object reference self.
그러니까, with 이후의 closure의 파라미터를 자기 자신으로 해서 실행할 수 있다는 것인데, 문서를 보면 제 1의 용법이 객체 생성 및 초기화라기 보다는 생성 이후 자신을 대상으로 추가적인 작업이 필요한 경우에도 유용하게 쓰일 수 있다. 실제로 문서의 용법 및 with 메소드의 구현은 다음과 같다.
// with의 용법
def b = new StringBuilder().with {
append('foo')
append('bar')
return it
}
// with의 내부 구현
public static <T,U> T with(
@DelegatesTo.Target("self") U self,
@DelegatesTo(value=DelegatesTo.Target.class,
target="self",
strategy=Closure.DELEGATE_FIRST)
@ClosureParams(FirstParam.class)
Closure<T> closure) {
return (T) with(self, false, (Closure<Object>)closure);
}
// with의 internal 구현
public static <T,U extends T, V extends T> T with(
@DelegatesTo.Target("self") U self,
boolean returning,
@DelegatesTo(value=DelegatesTo.Target.class,
target="self",
strategy=Closure.DELEGATE_FIRST)
@ClosureParams(FirstParam.class)
Closure<T> closure) {
@SuppressWarnings("unchecked")
final Closure<V> clonedClosure = (Closure<V>) closure.clone();
clonedClosure.setResolveStrategy(Closure.DELEGATE_FIRST);
clonedClosure.setDelegate(self);
V result = clonedClosure.call(self);
return returning ? self : result; // returning 파라미터 값에 따라 자신을 반환할지, 마지막 statement의 결과를 반환할지 결정한다.
}
위 용례와 같이 StringBuilder를 이용해서 String을 생성할 때에도 사용할 수 있다.
주의해야 할 점은, 명시적으로 결과를 return 해주지 않으면 엉뚱한 값이 선언될 수도 있다는 것인데, 최상단 예제에서 return 구문이 빠지면 article 오브젝트는 어떤 값을 가지게 될까?
바로.. 1이 된다..! (return numOfViewer = 1 이라고 생각하면 됨.)
이러한 실수를 방지하기 위해서는 with(true) { ... } 와 같이 사용하면 자기 자신을 반환하는 tap 메소드와 동일하게 사용할 수 있다.
마지막으로 Groovy tap을 이용한 방법이다. 마찬가지로 tap method에 대해서는 다음과 같이 설명하고 있다.
Allows the closure to be called for the object reference self (similar* to with) and always returns self.
설명에서도 보면 알 수 있지만, with과 굉장히 유사하며 한 가지 차이점은 언제나 자신을 리턴한다는 것이다. 그래서 코드를 보면 알 수 있지만, 명시적으로 자신을 리턴하는 코드가 생략되어 있다.
마찬가지로 tap의 내부 구현을 보면..
// tap의 용례
def b = new StringBuilder().tap {
append('foo')
append('bar')
}
// tap의 내부 구현
public static <T,U> U tap(
@DelegatesTo.Target("self") U self,
@DelegatesTo(value=DelegatesTo.Target.class,
target="self",
strategy=Closure.DELEGATE_FIRST)
@ClosureParams(FirstParam.class)
Closure<T> closure) {
return (U) with(self, true, (Closure<Object>)closure);
}
설명에서도 이미 말했지만, 내부 동작은 with 메소드와 동일하며, 유일한 차이는 명시적으로 return 하지 않아도 객체 자기 자신이 반환된다는 것이다. 따라서 객체 생성 및 초기화에는 tap 메소드가 조금 더 적합한 것으로 보인다.
다음으로는 Groovy의 핵심 기능이자, 최근 Kotlin으로 마이그레이션 도중 가장 대치가 힘들었던 Closure에 대해 알아보고자 한다~
'개발' 카테고리의 다른 글
[취미] 정부 사이트 캡챠(Captcha) 뚫기 (0) | 2020.09.29 |
---|---|
Kotlin의 Generic에 대하여.. (1) (0) | 2019.07.29 |
What is Groovy? (0) | 2019.07.27 |