저자: 김대곤
디자인 패턴은 매력적인 주제이다. 그리고 어려운 주제이다. 누구나 패턴을 공부하다보면, 반드시 만나게 되는 책이 있다. 이 책의 두 가지 문제점은 영어로 된 원서라는 것과 자바 예제를 제공하고 있지 않다는 점이다. 하지만 디자인 패턴을 설명한 책 중에서 아직도 가장 널리 사용되어지고, 사랑받고 있다. 많은 사람들이 예상하듯이 이 책은 Gang of Four(Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides)가 쓴 "Design Patterns: Elements of Reusable Object-Oriented Software(1995), Addison-Wesley"이다. 내가 이 책을 처음 본 것이 몇 년 전이고, 그 이후에도 한 번 이상은 본 것 같은데. 아직도 이해하지 못하는 부분들이 많다. 특히 실제로 사용하지 않았던 패턴들의 설명은 보고 돌아서면 도무지 생각이 나지 않는다.
하지만, 지금은 몇몇 패턴들은 거의 외우고 있고, 문제에 부딪혔을 때 사용할 수 있을 정도는 되었다. 아니 멋드러지게 변형해서 사용하기도 하고, 주로 몇 개의 패턴을 연결해서 사용한다. 나머지 패턴에 대해서 이해하려고 안달하지도 않고, 적어도 나에겐 쓸모없는 패턴이라고 생각하고 볼 생각도 하지 않는다. 아주 가끔 각 장의 처음 몇 줄을 읽어보긴 하지만. 이러한 현상은 패턴의 정의에서 보면 당연해 보인다. 정의에 따르면, 패턴은 "특정한 컨텍스트에서 자주 발생하는 문제에 대한 해결책"이다. 특정한 컨텍스트을 만나지 않으면, 그 컨텍스트에 정의된 패턴은 알아야 될 필요가 없다. 그리고 만나는 컨텍스트는 자주 만나기 때문에 항상 몇몇 패턴들만 사용한다. 내년에는 Visitor 패턴을 공부해야 할 것이다. 내년에는 컴파일러를 만들어 볼 생각하고 있다. Visitor 패턴은 컴파일러를 만들 때 유용한 패턴이다.
짧은 경험과 패턴을 어려운 하는 주위 사람들을 볼 때 패턴이 어려운 주요 이유는 해결책을 이해하지 못해서가 아니라 컨텍스트와 문제를 이해하지 못해서 이다. 왜 패턴을 써야하는지, 언제 패턴을 써야 하는지 모르는 것이다. 책을 봐도 여전히 이해가 되지 않는 것은 패턴을 설명하고 있는 대부분의 자료들이 패턴을 모두 디자인 단계에서 다루고 있기 때문이다. 디자인 패턴이 디자인 단계에서 쓰이는 것은 분명하다. 문제는 패턴이 정의된 컨텍스트와 해결하고자 하는 문제는 코드 단계에서 발생하는 것이고, 그것을 코드 단계 전에 해결하려고 하는 것이 디자인 패턴이기 때문이다. 결국 문제와 컨텍스트는 디자인 단계에서 보면 대단히 모호해 보이고, 문제 같아 보이지도 않는다는 것이다. 실제로 패턴을 적용하면 디자인 클래스 다이어그램은 더욱 복잡해진다.
필자는 일련의 패턴에 관한 기사들을 쓸 생각이다. 코드 단계에서 패턴을 살펴보고, 패턴을 쓰면 좋은 점은 무엇이고, 패턴을 언제 써야하는지를 보여주는 것을 목표로 해서, 필요하다면 디자인 단계에서 해결책들이 어떻게 보이지는 살펴보자.
Singleton 패턴은 가장 간단하면서도, 가장 많이, GUI를 사용한다면 반드시 사용해야 하는 패턴이다. 다른 패턴에서도 자주 Singleton 패턴을 이용하기도 한다. 이 패턴이 왜 필요한지, 언제 써야하는지 이해하기 어렵다면, Object에 대한 오해에서 비롯된 것이라 믿는다.
Object에 대한 오해
Object는 귀신과 같은 존재이다. "귀신 같다"는 것은 한 번 나타났다 사라지면, 언제 다시 볼 지 알 수 없는, 통제할 수 없는 미지의 세계 속으로 빠져버린다는 말이다. 그러나 많은 사람들은 자신이 원할 때 언제나 꺼내서 쓸 수 있는 지갑 속의 화폐처럼 생각하는 경향이 있다. 다음 예제는 Object를 생성하는 Frame(CreateObject)과 Object를 사용할 Frame(ObjectUser)를 나눈 것이다. 목표는 CreateObject에서 생성한 SimpleObject를 ObjectUser에서 보는(찾는) 것이다. 데이타베이스, Object Serialization 등 어떠한 방법을 써도 무방하다.
Object가 같다는 것은 Object가 가진 데이터, attributes가 같다는 뜻이 아니라, Hashcode가 같아야 한다. 아래의 화면에서 보이는 객체는 동일한 클래스의 객체이며, attribute에 같은 값을 가지고 있으나, CreateObject가 생성한 객체는 아니다. 만약 동일한 객체라면 hashcode가 같을 것이다. 소스의 나머지 부분들은 GUI를 위한 것이고, 각 Frame이 가지고 있는 actionPerformed() 메소드만 보면 된다. 아래는 CreateObject.java의 actionPerformed() 메소드에 있는 소스코드이다. 직접 해결해 보기 바란다. 그러면 나머지는 그냥 이해되리라 믿는다.
String name = inputName.getText();
String value = inputValue.getText();
SimpleObject simple = new SimpleObject(name, value);
tablePanel.update(simple);
[Download Source - 1] : 소스는 패키지를 사용하였음.
Global access 와 Only one instance of a class
Singleton 패턴을 쓰는 목적은 Global access와 하나의 객체만을 생성되도록 강제하는데 있다. 사실 이것은 동전의 양면과 같다. 정확한 표현은 아니지만, 하나의 객체만 있기 때문에 Global access가 가능하기 때문이다. 위의 문제에 대해 심각하게 고민해 봤다면, 이 문제를 해결하기 위해서 소스가 얼마나 더러워(복잡해)지는 알 수 있을 것이다. Singleton 패턴은 귀신 같은 Object를 지갑 속의 화폐로 만들어 준다. 주의할 점은 객체를 새로운 객체를 생성해서 찾을 수는 없다는 것이다. 예를 들면, java.util.Vector에 들어있는 객체를 찾을 때, 모든 attribute가 동일한 값을 가지는 새로운 객체(object)를 만들어서 Vector.contains(object)를 쓸 수 없다는 뜻이다. 새로운 객체를 만드는 순간 그 객체를 전 시스템에서 유일한 객체이고, java.util.Vector에 들어 있는 객체들 중에 하나는 아닌 것이다.
Solutions
Singleton 패턴이 적용된 클래스의 구조는 다음과 같다.
Singleton이 적용된 클래스는 세가지 구조를 갖는다. 첫번째는 자기 자신의 클래스 타입으로 선언된 static 변수를 가진다. 이것은 UML에서 자기 자신과 Association를 갖는 것으로 나타난다. 두 번째는 외부에서 Singleton 클래스의 객체를 생성하지 못하도록 생성자를 private으로 선언한다. 마지막으로 자기 자신의 객체를 반환하는 static method(getInstance())를 제공한다. 꼭 getInstance일 필요도 없고, instance일 필요도 없다. 단지 자기 자신의 객체(static 변수에 저장된 객체)를 반환하는 static method이면 된다. 나머지 구조들은 필요에 따라 만들기도 하고, 없어지기도 한다.
Design Pattern 원서에는 Singleton 패턴을 사용하는 세 가지 방법이 나온다. 위에 설명한 것이 가장 일반적인 방식이다. 두 번째는 static method 대신 instance-side method를 사용하는 것이다. 하지만, instance-side method는 자바에서 지원하지 않는 것 같고, 다른 프로그램 언어인 Smalltalk에서 지원되는 것으로 알고 있다. 이런 방식을 사용하는 이유는 static를 사용하는 경우, 상속에 제약을 주기 때문이다. 세 번째 방식은 Registry를 사용하는 것으로 가장 유연한 방법이다. 그러나 Registry 역할을 하는 것이 있던가 만들어야 한다.
소스를 보면 알게 되겠지만, Singleton 클래스는 여섯 개 이상의 SimpleObject 객체는 저장하지 못하도록 구현되었다. 그래서 ObjectUser에서는 일곱 번째 이후에 생성된 클래스는 불러서 쓸 수가 없다. 여기서 SimpleObject 객체의 생성을 GUI에서 할 수 없도록 고치면 똑 같은 방식으로 사용자가 일곱 개 이상의 객체를 만드는 것을 제약 수 있다. 만약 이 때 SimpleObject 객체를 하나만 가질 수 있도록 하면, 실제로 SimpleObject가 Singleton이 적용되지 않은 클래스라 하더라도 Singleton처럼 사용할 수 있다. 이런 메카니즘에 Registry를 이용하는 세 번째 방식이다. 하여튼 Singleton 패턴이 시스템이 가질 수 있는 객체 수를 제한할 때, 하나가 아니라 하더라도 언제든지 사용할 수 있다.
[Download Source - 2]
왜 항상 사용되어지지 않는가?
그러면 이런 의문에 부딪히게 된다. "이렇게 중요하고 반드시 필요한 패턴이라면, 내가 아직도 모르고 있었나?" 필자 생각에는 두 가지 이유 때문이다. 하나는 마치 Singleton처럼 행동하는 것을 사용하고 있기 때문이고, 객체를 일종의 함수처럼 사용하기 때문이다.
Singleton처럼 행동하는 것은 데이터베이스를 의미한다. 데이터베이스는 거대한 Singleton이다. 같은 객체를 사용할 수 있도록 해 주진 않지만, 자기자신은 하나이고, 실제 객체는 다르지만, 마치 같은 객체를 사용하고 있는 듯 각 attribute의 값을 동일하게 유지할 수 있도록 한다. 한 객체가 생성되어, 그 값들을 데이터베이스에 저장하면, 필요할 때는 값들을 가지고 다시 객체를 만든다. 그러면 정보의 입장에서 보면 그 객체가 부활한 것이다.
객체를 함수처럼 사용하다는 의미를 살펴보자. 게시판의 리스트를 조회하는 객체를 보면, 그 객체가 하는 일은 데이터베이스 또는 파일에서 정보를 읽어서 화면에 보여주는 역할만 할 뿐이다. 그 객체가 정보를 저장하고 있지 않기 때문에 함수와 같은 역할을 한다는 것이다. 데이터베이스이든 파일이든 Persistence를 제공하는 매카니즘을 사용하지 않고 작은 시스템을 개발해 보면 Singleton 패턴을 이해하는데 도움이 될 것이다.
다른 용도
그렇다고 Singleton 패턴이 전혀 사용되어지지 않는 것은 아니다. 먼저, 성능 향상에 사용된다. 데이터베이스에서 정보를 읽어오는 일은 무거운 작업이다. 한 번 읽어서 계속 사용되는 정보는 데이터베이스에서 읽어서 Singleton 객체가 가지고 있으면 언제든지 누구든지 사용할 수 있다.
두 번째는 데이터베이스에 저장시 Key값을 가져오기 위해 사용된다. 처음에 Max값을 가지고 와서, Key값이 요청될 때마다 값을 증가시키면, Singleton은 오직 하나의 객체가 존재함으로 Key의 유일성이 보장된다.
결어
마치 Singleton패턴이 사양길에 접어든 것처럼 보일지 모르지만, 여전히 유효하고, 간단하면서도 강력한 패턴인 것은 분명하다.