1. Singleton
- 전역변수와 거의 비슷한 pros & cons를 가짐
- 생성자가 여러 차례 호출되더라도 실제 생성되는 객체는 하나이고 최초의 생성자가 생성한 객체를 리턴
1.1. 의도
- Singleton은 클래스에 인스턴스가 하나만 있도록 하면서 이 인스턴스에 대한 전역 액세스를 제공하는 디자인 패턴
1.2. 문제
- Singleton은 한 번에 두 가지의 문제를 동시에 해결함으로써 Single Responsibility Principle을 위반함
- 클래스에 인스턴스가 하나만 있도록 함
- 생성자 호출은 반드시 새 객체를 반환해야 하므로 이는 일반 생성자로 구현할 수 없음
- 해당 인스턴스에 대한 전역 접근을 제공
- 전역 변수들을 잠재적으로 변수를 덮어쓸 수 있고 그로 인해 충돌이 발생할 수 있음
1.3. 해결책
- 다른 객체들이 Singleton class와 함께 new 연산자를 사용하지 못하도록 default 생성자를 private에 넣음
- 생성자 역할을 하는 static 메소드를 만들어서, 메소드 내부에서 private에 넣은 default 생성자를 만들고 그다음 호출들은 캐시 된 객체를 반환
2. 구조
- Singleton class는 static 메소드인 getInstance()를 선언하고 default 생성자는 private로 숨겨야 함
3. 사용
- 프로그램의 클래스에 모든 클라이언트가 사용할 수 있는 단일 인스턴스만 있어야 할 경우에 사용
- 예를 들면 프로그램의 다른 부분들에서 공유되는 단일 데이터베이스 객체
- 싱글톤 패턴은 getInstance() 함수를 제외하고 클래스 객체들을 생성할 수 있는 모든 다른 수단들을 비활성화 해야 됨
4. Pros and Cons
4.1. Pros
- 클래스가 하나의 인스턴스만 갖는다는 것을 확신할 수 있음
- 인스턴스에 대한 전역 접근 지점을 얻음
- 해당 객체는 처음 요청될 때만 초기화됨
4.2. Cons
- Single Responsibility Principle을 위반
- 잘못된 디자인을 가릴 수 있음 (예를 들어 프로그램 컴포넌트들이 서로에 대해 많이 아는 경우)
- 다중 스레드 환경에서 여러 스레드가 싱글톤 객체를 여러번 생성하지 않도록 핸들링 필요
- 클라이언트 코드를 유닛 테스트하기가 어려울 수 있음
- 많은 테스트 프레임워크들이 mock 객체들을 생성할 때 상속에 의존하기 때문
- 싱클톤 클래스의 생성자는 private이고 대부분의 언어에서 static method를 오버라이딩 하는 것이 불가능
5. 코드로 알아보기
#include <iostream>
#include <string>
// Singleton 클래스
class Singleton
{
private:
Singleton(const std::string value): value_(value)
{
std::cout << "Singleton constructor" << "\n";
}
static Singleton* singleton_;
std::string value_;
public:
// copy constructor 방지
Singleton(Singleton &other) = delete;
// copy assignement 방지
void operator=(const Singleton &) = delete;
// static 함수로 singleton 객체를 컨트롤함
// static 함수로 하는 이유는 초기에 객체가 없어도 함수 콜을 위해
static Singleton *GetInstance(const std::string& value);
void SomeBusinessLogic()
{
// ...
}
std::string value() const{
return value_;
}
};
// 초기에 nullptr로 설정
Singleton* Singleton::singleton_= nullptr;;
// GetInstance 함수 바디 구현
Singleton *Singleton::GetInstance(const std::string& value)
{
// singleton 객체가 있는지 없는지 유무를 체크하여 없으면 초기에 1번 생성
// 다음부턴 존재하는 객체를 사용
if(singleton_ == nullptr){
singleton_ = new Singleton(value);
}
return singleton_;
}
int main()
{
Singleton* singleton = Singleton::GetInstance("FOO");
std::cout << singleton->value() << "\n";
singleton = Singleton::GetInstance("BAR");
std::cout << singleton->value() << "\n";
return 0;
}
#include <iostream>
#include <mutex>
#include <thread>
class Singleton
{
// private으로 instance와 mutex를 관리
private:
static Singleton * pinstance_;
static std::mutex mutex_;
protected:
Singleton(const std::string value): value_(value)
{
}
~Singleton() {}
std::string value_;
public:
// copy 생성자 대입연산자 불가능
Singleton(Singleton &other) = delete;
void operator=(const Singleton &) = delete;
// static 메소드
static Singleton *GetInstance(const std::string& value);
void SomeBusinessLogic()
{
// ...
}
std::string value() const{
return value_;
}
};
Singleton* Singleton::pinstance_{nullptr};
std::mutex Singleton::mutex_;
// lock_guard를 통해 스레드 간 접근 제어
Singleton *Singleton::GetInstance(const std::string& value)
{
std::lock_guard<std::mutex> lock(mutex_);
if (pinstance_ == nullptr)
{
pinstance_ = new Singleton(value);
}
return pinstance_;
}
void ThreadFoo(){
// Following code emulates slow initialization.
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
Singleton* singleton = Singleton::GetInstance("FOO");
std::cout << singleton->value() << "\n";
}
void ThreadBar(){
// Following code emulates slow initialization.
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
Singleton* singleton = Singleton::GetInstance("BAR");
std::cout << singleton->value() << "\n";
}
int main()
{
std::cout <<"If you see the same value, then singleton was reused (yay!\n" <<
"If you see different values, then 2 singletons were created (booo!!)\n\n" <<
"RESULT:\n";
std::thread t1(ThreadFoo);
std::thread t2(ThreadBar);
t1.join();
t2.join();
return 0;
}
참고
wikipedia
refactoring.guru
This is personal diary for study documents.
Please comment if I'm wrong or missing something else 😄.
Top
댓글남기기