본문 바로가기
프로그래밍 놀이터/디자인 패턴, 리펙토링

[Design Pattern/Java] 가변성을 최소화 하자.

by 돼지왕 왕돼지 2012. 3. 27.
반응형



안녕하세요 돼지왕왕돼지입니다.

오늘은 "가변성을 최소화 하자" 라는 주제로 이야기해볼까 합니다.

이 글은 "Effective Java" 의 내용을 정리한 글입니다.


불변 클래스가 필요한 이유

불변 클래스는 가변 클래스에 비해 설계와 구현 및 사용이 더 쉽습니다. 또한 에러 발생이 적으며 보안이나 사용 측면에서 더 안전합니다. Thread-safe 한 점도 빼놓을 수 없죠.




불변 클래스를 만들 때 지켜야 하는 규칙

1. 객체의 상태를 변경하는 그 어떤 메소드도 제공하지 않는다.

2. 상속( inheritance )를 할 수 없도록 하자.
- class 를 final 로 선언할 수도 있고, 생성자를 private 으로 만들고, static factory 메소드를 제공할 수도 있다.


3. 모든 필드를 final 로 지정한다.

4. 모든 필드를 private 으로 지정한다.
- 물론 public final 로 지정하는 방법도 있지만, 이는 향후 클래스 내부 구조를 변경하는 것이 어렵기 때문에 바람직하지 않습니다.


5.  가변 컴포넌트의 직접적인 외부 접근을 막자.
- 만일 가변 객체를 참조하는 필드가 클래스에 있다면, 클라이언트가 어떤 식으로든 해당 가변 객체의 참조를 획득할 수 없게 해야 합니다. ( Client 가 주는 객체 참조로 그런 필드를 초기화해서도 안됩니다. ) 만약 어쩔 수 없이 객체의 참조를 받아야 하거나, 참조를 return 해주어야 한다면 방어 복사본 ( defensive copy ) 를 사용해야 합니다.




함수적 ( Functional ) 방법 과 절차적 ( Procedural ) 또는 명령적 ( Imperative ) 방법.

함수적 방법
메소드에서 피연산자를 변경하지 않고 함수를 적용한 결과를 반환하는 방법. 불변 클래스에서 잘 쓰는 방법이며, 이 함수적 방법으로 return 하는 녀석이 방어 복사본 ( Defensive Copy ) 와 연결됩니다. 


절차적 또는 명령적 방법
메소드에서 피연산자에 대한 처리를 수행하므로 피연산자의 값이 변경되는 방법. 




불변객체의 General 한 특성들.

- 불변 객체는 단순하여 생성될 당시의 상태 하나만을 가질 수 있습니다.

- 만일 불면 클래스의 모든 생성자에서 불변을 유지하는 데 필요한 규칙들을 확립한다면, 그 클래스 또는 그 클래스를 사용하는 프로그래머 측에서는 추가적인 노력 없이 불변성을 유지할 수 있습니다.

- 불변 객체는 본질적으로 변하지 않기 때문에 스레드에서 사용시 안전하여 동기화 (Synchronization) 이 필요 없습니다.

- 불변 클래스에서는 클라이언트가 기존 인스턴스를 재사용할 수 있다는 장점을 최대한 이용해야 하며, 이를 위한 쉬운 방법은 자주 사용되는 값을 public static final 상수로 제공하는 것입니다.

- 불변 클래스에서는 자주 사용되는 인스턴스들을 보관하고 재사용하는 caching 을 해주는 static factory method 를 제공할 수도 있습니다. 이 경우 기존 인스턴스가 사용 가능하면 새로운 인스턴스를 생성하지 않습니다. ( 메모리의 빈번한 할당과 해지를 줄이고 GC 의 부담도 덜게 됩니다. ) 

- 불변객체는 방어 복사본 ( defensive copies ) 를 만들 필요가 없어야 하며, clone 메소드나 복사 생성자 ( copy constructor ) 를 둘 필요도 없습니다. ( 만들어봐야 다 똑같은 녀석이니깐요 )

- 불변 객체는 다른 객체( 불변, 가변을 불문하고 )를 만들 때 사용할 수 있는 훌륭한 컴포넌트입니다. 




불변 객체의 단점은 없는 것인가?

유일한 단점은 객체가 가질 수 있는 각 값마다 별개의 객체가 필요하다는 것입니다. 여러 개의 객체를 생성하면 비용이 많이 들며, 객체가 크다면 특히 더 그렇습니다.  하여 성능상의 문제점이 나올 수 있는데 이에 대한 해결책은 두가지 방법이 있습니다.

1. 어떤 다단계 연산이 필요한지 알아내어 불변 객체 대신 기본형 데이터 타입을 사용하는 것입니다.
2. 성능 등의 관점을 고려하여 내부적으로 불변 클래스가 나름의 현명한 처리를 수행하는 것입니다. 하지만 이는 어떤 복잡한 다단계 연산을 원하는지 정확하게 알 수 있을 때 사용할 수 있는 방법입니다. 




불변성을 위해 subclass 를 만들지 못하도록 하는 방법.

class 자체를 final 로 지정하는 방법도 있고, 클래스의 모든 생성자를 private 으로 지정하고, public 생성자 대신 public static factory method 를 추가하는 것입니다. 두번째 방법은 흔히 사용되지는 않지만 가끔 최상의 대안이 됩니다. static factory method의 많은 장점들을 가져갈 수 있기 때문이지요. 

불변 클래스가 sub class 를 만들지 못하게 해야 하는 이유는 무엇일까요?

 
불변 클래스가 sub class 를 만들 수 있다면, 신뢰할 수 없는 클라이언트로부터 전달된 불변 클래스가 실제로 그 class 인지 sub class 인지를 점검해야 합니다. 그래서 잘못 만들어진 불변 클래스의 경우는 객체의 방어 복사를 사용해야 안전합니다. 방어복사란, 말 그대로 방어를 위한 복사인데, 여기서의 의미는, 전달되어온 class 그 자체를 새로 생성하여 사용하는 것입니다. subclass 여도 탈이 되지 않도록 말이죠. 




불변성 규칙에 예외란 없는 건가요?

불변 객체는 어떤 메소드에서도 자기의 상태를 변경해서는 안 되고 모든 필드는 final 이어야 한다고 명시하지만, 이런 규칙은 필요 이상으로 엄격하여 성능 향상을 위해 완화될 수 있습니다. 실제로 외부에서 접근할 수 있는 변경 메소드만 없으면 되는 것이지요. 그래서 일부 불변 클래스에서는 final 이 아닌 필드를 하나 이상 갖고 있으면서 최초 필요 시에 그 필드에 연산 결과를 보관합니다. 만일 같은 값이 다시 요청되면, 연산의 부담을 덜기 위해 보관된 값을 반환하는 식이죠.




불변 객체를 만들 때 추가적으로 주의해야 할 사항은 없나요?

직렬화 (Serialization) 인터페이스를 구현하면서 가변 객체를 하나 이상 참조하고 있다면 반드시 readObject나 readResolve 메소드를 명시적으로 정의해야 하며, ObjectOutputStream.writeUnshared 와 ObjectInputStream.readUnshared  메소드를 사용해야 합니다. 그렇지 않으면, 악의적인 코드에서 불변 클래스의 인스턴스를 가변 객체로 생성할 수 있습니다.




일반적으로 불변 클래스 만들기.

인스턴스가 가변적이어야 할 타당한 이유가 없다면, 그 클래스는 불변 클래스가 되어야 합니다. 불변 클래스는 많은 장점을 가지고, 유일한 단점은 성능문제를 야기할 수 있다는 것입니다. 그래서 성능문제를 초래할 수 있다는 타당한 이유가 없다면 모든 인스턴스를 불변으로 만들어 주시면 되겠습니다. 물론 현실적으로 불변 클래스가 되기 어려운 클래스들도 있습니다. 이런 경우에는 가능한한 가변성을 제한하면 되겠습니다. 물론 이렇게 함으로서 에러의 사능성이 줄어들지요. 다들 아시겠지만, 가능한한 가변성을 제한하는 방법은, 가변이 아닌 모든 필드에 final 을 선언해주는 것입니다.




도움이 되셨다면 손가락 꾸욱~




반응형

댓글