티스토리 뷰
유일무이한 인스턴스를 만들어 사용하기
프로그래밍을 하다 보면 여러 개의 클래스에서 한 개의 객체에 접근하고 싶을 때가 많습니다. 다음과 같은 클래스가 있다고 가정해봅니다.
Code – Singleton.cs |
class Singleton { private int a; public Singleton() { a = 0; } internal void SetA(int val) { this.a = val; } internal int GetA() { return a; } } |
간단한 클래스이니 주석이나 설명은 생략하겠습니다.
이 클래스의 객체를 생성하여 a값을 접근하는 두 개의 각각의 클래스가 있다고 생각해봅시다.
Code – ClassA.cs |
class ClassA { Singleton singleton; public ClassA() { singleton = new Singleton(); } internal void SetA(int val) { singleton.SetA(val); } internal void PrintA() { Console.WriteLine(singleton.GetA()); } } |
Code – ClassB.cs |
class ClassB { Singleton singleton; public ClassB() { singleton = new Singleton(); } internal void PrintA() { Console.WriteLine(singleton.GetA()); } } |
ClassA에만 SetA함수가 존재하는 것 말고는 ClassA와 ClassB는 모두 Singleton클래스의 객체를 만들어 a값을 사용하는 동일한 클래스들입니다. (사실 프로그래밍을 하다 보면 다음 예제보다는 훨씬 복잡해지겠죠..)
그럼 메인 프로그램을 보겠습니다.
CODE – Program.cs |
class Program { static void Main(string[] args) { ClassA a = new ClassA(); a.SetA(10); a.PrintA(); ClassB b = new ClassB(); b.PrintA(); } } |
프로그램에서는 ClassA와 ClassB의 객체를 생성하여 a값을 셋팅하고 프린트 합니다.
제가 원하던 결과는
10
10
이 나오는 것이었습니다.
하지만 무심하게도 결과는
10
0
이 나오지요..
지금과 같은 경우 각각 ClassA와 ClassB에서 Singleton클래스의 객체를 따로따로 생성해주기 때문에 생기는 문제입니다.
그렇다면 ClassA와 ClassB에서 Singleton클래스의 객체를 따로 생성하지 않도록 코드를 변경하면 원하는 결과를 얻을 수 있을 것입니다.
ClassA와 ClassB에서 Singleton의 객체를 생성하지 않고 생성자에서 객체를 받아 사용하는 방법으로 변경해 보도록 하겠습니다.
Code – 변경된 ClassA.cs |
class ClassA { Singleton singleton; public ClassA(Singleton s) { this.singleton = s; } internal void SetA(int val) { singleton.SetA(val); } internal void PrintA() { Console.WriteLine(singleton.GetA()); } } |
Code – 변경된 ClassB.cs |
class ClassB { Singleton singleton; public ClassB(Singleton s) { this.singleton = s; } internal void PrintA() { Console.WriteLine(singleton.GetA()); } } |
변경된 ClassA와 ClassB를 사용하기 위해서는 각각의 생성자에 Singleton객체를 넘겨주어야 합니다. 하나의 Singleton클래스 객체를 생성하여 각각의 생성자에 넘겨누는 최종 프로그램의 소스를 만들어 보겠습니다.
Code – Program.cs |
class Program { static void Main(string[] args) { Singleton singleton = new Singleton(); ClassA a = new ClassA(singleton); a.SetA(10); a.PrintA(); ClassB b = new ClassB(singleton); b.PrintA(); } } |
자 이제 우리가 원하는 결과를 얻을 수 있겠지요.
지금까지의 방법이 지금껏 제가 사용하던 방법이었습니다. 하지만 이 방법에는 큰 단점이 있는데, 공유할 객체가 한 개 내지 두 개와 같이 적을 경우엔 괜찮겠지만 여러 개의 객체를 공유해야 하는 경우 생성자가 쓸데없이 복잡해질 뿐 아니라 소스가 계속 변경되고, 슬슬 머리가 아파오기 시작한다는 것입니다.
이것을 위해 객체를 하나만 만들어 사용할 수 있도록 Singleton이란 패턴이 존재합니다. (저도 오늘 책보면서 알게 되었습니다.) 그리고 이것을 알려드리고자 합니다.
아까 변경전의 소스에서 조금만 손을 본다면 ClassA와 ClassB의 생성자에 객체를 넘기지 않고도 한 개의 객체를 각각의 클래스에서 공유해서 사용할 수 있습니다.
먼저 한 개 이상의 객체가 생성되지 못하도록 Singleton클래스를 수정합니다.
최종 변경 Code – Singleton.cs |
class Singleton { private int a; private static Singleton singleton; private Singleton() { a = 0; } public static Singleton GetInstance() { if (singleton == null) singleton = new Singleton(); return singleton; } internal void SetA(int val) { this.a = val; } internal int GetA() { return a; } } |
이 소스를 보면 의아한 점이 생길 것입니다. 객체를 생성하기 위해 new 를 사용하면 생성자에 접근하게 되는 데, 소스에서 보이는 생성자의 접근자가 private으로 되어있습니다. (private과 public 의 차이가 정확히 생각나지 않는 분은 그 공부부터 하세요) 접근자가 private으로 되어있다는 얘기는 외부에서 호출할 수 없다는 것입니다. 즉 다른 클래스에서 Singleton s = new Singleton();과 같이 사용할 수 없다는 것이죠.
그렇다면 어떠한 방법으로 클래스의 객체를 생성하게 될까요? 답은 이곳에 있습니다.
private static Singleton singleton;
public static Singleton GetInstance()
{
if (singleton == null) singleton = new Singleton();
return singleton;
}
Singleton클래스의 내부에 전역적으로 선언된 static 객체가 있고, 이것을 리턴해주는 GetInstance() 메소드가 보이네요. 이 메소드 내부에서 new Singleton()을 통해 객체를 생성하는 것을 볼 수 있습니다. ‘아까 new를 통해서 생성할 수 없다고 하지 않았습니까?’ 아까도 말씀드렷다시피 new 를 사용하게 되면 생성자를 통해 객체를 생성한다고 하였는데 이와 같은 경우 클래스 내부에서 자신의 생성자에 접근하는 꼴이 되기 때문에 생성자가 private이라도 접근할 수 있는 것입니다. 이해가 될는지 모르겠네요.
외부의 클래스에서 Singleton의 객체를 사용하기 위해서는 new 연산자를 통해 객체를 생성하는 것이 아니라 Singleton.GetInstance() 메소드를 호출해야 할 것입니다. 이제 변경된 Singleton클래스의 객체를 사용하기 위해 ClassA와 ClassB를 수정하겠습니다.
최종 변경 Code – ClassA.cs |
class ClassA { Singleton singleton; public ClassA() { singleton = Singleton.GetInstance(); } internal void SetA(int val) { singleton.SetA(val); } internal void PrintA() { Console.WriteLine(singleton.GetA()); } } |
최종 변경 Code – ClassB.cs |
class ClassB { Singleton singleton; public ClassB() { singleton = Singleton.GetInstance(); } internal void PrintA() { Console.WriteLine(singleton.GetA()); } } |
Program.cs는 변경 전 처음과 같습니다.
최종 변경 Code – Program.cs |
class Program { static void Main(string[] args) { ClassA a = new ClassA(); a.SetA(10); a.PrintA(); ClassB b = new ClassB(); b.PrintA(); } } |
그리고 원하는 결과를 얻을 수 있었습니다.
10
10
지금까지 알려드린 방법이 Singleton패턴의 가장 고전적인 방식이며 기초가 되는 방법입니다.
이렇게 사용하였을 경우 단일 프로그램에서는 잘 돌아가겠지만, 다중 스레드 프로그래밍 모델에서는 잘 돌아가지 않을 수 있습니다. (이것이 해결된 버전은 따로 문의를 주거나 싱글턴 패턴을 검색하여 좀 더 살펴보시기 바랍니다.)