싱글톤(Singleton) 패턴은 객체의 인스턴스를 하나만 생성하고, 해당 인스턴스에 전역적으로 접근할 수 있게 하는 디자인 패턴입니다. 즉, 시스템 내에서 해당 클래스의 인스턴스가 오직 하나만 존재하도록 보장하는 구조를 제공합니다.
1. 싱글톤 패턴의 필요성
- 자원의 낭비를 방지: 여러 개의 인스턴스가 필요 없는 상황에서 불필요한 메모리 낭비를 줄입니다. 예를 들어, 데이터베이스 연결을 관리하는 클래스나 로깅 시스템은 여러 개의 인스턴스가 필요하지 않습니다. 이러한 클래스는 단 하나의 인스턴스만 있어도 충분합니다.
- 전역적 접근: 싱글톤 인스턴스는 애플리케이션 어디에서든지 접근할 수 있습니다. 이는 클래스 내에 객체를 전역적으로 관리하고자 할 때 유용합니다.
2. 싱글톤 패턴의 구현
자바에서 싱글톤 패턴은 보통 정적 메서드와 정적 필드를 사용해 구현됩니다. 이를 통해 클래스의 인스턴스가 처음 요청될 때 생성되고, 이후에는 동일한 인스턴스를 반환합니다.
싱글톤 패턴 구현 예제
public class UserServiceImpl {
// 클래스의 유일한 인스턴스를 저장할 정적 필드
private static UserServiceImpl instance = new UserServiceImpl();
// 외부에서 생성자를 호출하지 못하도록 private 생성자를 정의
private UserServiceImpl() {}
// 정적 메서드로 유일한 인스턴스를 반환
public static UserServiceImpl getInstance() {
return instance;
}
}
구현의 주요 특징:
- 정적 필드: 클래스 내에 static 키워드를 사용해 유일한 인스턴스를 생성 및 보관.
- 생성자(private): 외부에서 new 키워드를 통해 인스턴스를 추가로 만들지 못하게 생성자를 private으로 설정.
- 정적 메서드: getInstance()라는 정적 메서드를 통해 어디서든지 인스턴스에 접근 가능.
이 구현에서 중요한 점은 하나의 객체만 생성되며 그 객체는 프로그램 전체에서 공유된다는 것입니다.
3. 싱글톤 패턴에서 인터페이스 사용
이제 싱글톤 패턴의 기본 개념을 이해했으니, 다음으로는 인터페이스와 구현체를 어떻게 사용할 것인지에 대해 설명하겠습니다. 아래 두 가지 코드를 비교해보겠습니다.
(1) 인터페이스 타입으로 싱글톤을 반환하는 방식
public class UserServiceImpl implements UserService {
private static UserService instance = new UserServiceImpl();
private UserServiceImpl() {}
public static UserService getInstance() {
return instance;
}
}
- **인터페이스 UserService**를 반환합니다.
- UserService는 인터페이스이므로, 그 하위의 다른 구현체로도 확장 가능.
- 이 방식은 클라이언트 코드가 구체적인 구현체(UserServiceImpl)에 의존하지 않고, 인터페이스를 통해 객체를 다룹니다.
- 유연한 구조를 제공하므로, 유지보수가 쉽고 나중에 다른 구현체로 바꾸는 것도 편리합니다.
(2) 구현체 타입으로 싱글톤을 반환하는 방식
public class UserServiceImpl {
private static UserServiceImpl instance = new UserServiceImpl();
private UserServiceImpl() {}
public static UserServiceImpl getInstance() {
return instance;
}
}
- 이 코드는 인터페이스가 아닌 **구체적인 클래스(UserServiceImpl)**를 반환합니다.
- 클라이언트 코드가 UserServiceImpl 클래스에 종속됩니다.
- 즉, 나중에 UserServiceImpl이 아닌 다른 구현체를 사용하고 싶다면, 클라이언트 코드 전체에서 해당 클래스를 수정해야 합니다.
4. 인터페이스 반환과 구현체 반환의 차이
인터페이스 타입으로 반환할 때의 장점:
- 유연성: 인터페이스를 반환하면 언제든지 다른 구현체로 변경할 수 있습니다. 예를 들어, UserServiceImpl 대신에 MockUserService를 구현하여 교체 가능.
- 의존성 역전 원칙(DIP) 준수: 객체가 구체적인 구현체에 의존하지 않고 인터페이스에 의존함으로써 확장 가능성이 높아집니다.
- 테스트 용이성: 유닛 테스트에서 모의 객체(mock)를 쉽게 주입할 수 있습니다.
구현체 타입으로 반환할 때의 단점:
- 구체적인 구현에 종속: 클라이언트 코드가 특정 구현체에 종속됩니다. 만약 다른 구현체로 변경해야 할 경우, 코드를 크게 수정해야 합니다.
- 유연성 부족: 인터페이스를 사용하지 않기 때문에 다른 구현체로 확장하기 어렵습니다.
5. 결론: 언제 인터페이스를 쓰고, 언제 구현체를 쓰나?
- 인터페이스 반환 방식을 사용하면 유지보수성과 유연성이 높습니다. 특히 프로젝트가 커지고, 여러 개발자가 작업하거나, 테스트 주도 개발(TDD)을 할 때 유리합니다.
- 구현체 반환 방식은 코드가 단순할 때 적합하지만, 변경 가능성이 적거나 구체적인 구현체에 강하게 의존할 필요가 있을 때 사용됩니다.
따라서 싱글톤 패턴을 구현할 때는 인터페이스를 사용한 유연성 있는 코드 구조를 권장합니다.
'개발 > JAVA' 카테고리의 다른 글
[생활코딩] 제어문 - 문자의 비교 : ==과 equals의 차이점 (0) | 2024.07.02 |
---|---|
[생활코딩] 제어문 - 조건문 (0) | 2024.07.02 |
[생활코딩] 제어문 - 비교연산자 (0) | 2024.07.02 |
[생활코딩] 제어문 - boolean (0) | 2024.07.02 |
[JAVA] 입력과 출력 (0) | 2024.07.01 |