1️⃣ Singleton Pattern
Singleton Pattern (싱글톤 패턴) 은 하나의 클래스가 오직 하나의 인스턴스만 가지는 패턴이다.
싱글톤 인스턴스는 최초 생성시 하나만 만들어지며, 해당 인스턴스를 다른 모듈이 공유하며 사용한다.
Java에서 가장 기본적인 싱글톤의 형태는 다음과 같다.
public class Singleton{
//객체는 하나의 인스턴스만 가진다.
private static Singleton instance = new Singleton();
//생성자를 'private'로 제한하여 외부에서 싱글톤 객체를 만들지 못하게 한다.
private Singleton() {
}
//get 메서드만을 통해서 싱글톤 객체를 얻을 수 있다.
public static Singleton getInstance() {
return instance;
}
}
필드인 instance 를 보면 Singleton 객체를 하나 생성하는데, 다른 모듈은 이를 공유하여 사용한다.
여기서 중요한 점은 생성자를 'private' 으로 제한한다는 것이다. 따라서 싱글톤 인스턴스를 얻기 위해선 'getInstance()' 메서드를 사용해야 한다.
필드와 getInstance() 메서드만 'static' 으로 설정한 이유는 무엇일까?
외부에서 싱글톤 객체를 직접 생성할 수 없다고 하였다. 즉, 객체 내부에 미리 생성된 인스턴스를 공유하는 개념이다. 이러한 이유로 new 연산자를 통해 객체를 생성할 수 없다.
따라서 외부에서 싱글톤 객체를 만들지 않고 사용하려면 static instance 를 만들어야 한다. 또한 외부에서 싱글톤 객체를 만들지 않고 받아오려면 메서드를 static 으로 만들어야 한다.
즉, 외부에서 싱글톤 객체를 생성할 수 없다는 점, 공유한다는 이유에서 static 으로 설정한 것이다.
public class Main{
public static void main(String[] args) {
Singleton s1=Singleton.getInstance();
Singleton s2=Singleton.getInstance();
System.out.print(s1==s2);
}
}
만약 위 코드를 실행한다면 결과값은 true가 나올 것이다.
또한 'toString()' 나 'hashCode()' 메서드를 사용하면 정확하게 같다는 것을 알 수 있다.
public class Main{
public static void main(String[] args) {
Singleton s1=Singleton.getInstance();
Singleton s2=Singleton.getInstance();
System.out.println(s1.toString());
System.out.println(s2.toString());
}
}
/**
* 결과
* project1.Singleton@7dc36524
* project1.Singleton@7dc36524
*/
2️⃣ 장점
- 최초 생성된 인스턴스를 공유하여 사용하기 때문에 인스턴스를 생성하는 비용이 줄어든다.
- 데이터베이스 연결 모듈 등 실용적으로 사용할 수 있다.
3️⃣ 단점
- 공유 인스턴스이기 때문에 모듈 간 결합을 강하게 만들 수 있다. -> DI 를 통해 해결할 수 있다.
- TDD (Test Driven Development)를 할 때 단위 테스트를 하는데, 싱글톤 패턴은 독립적인 인스턴스를 만들기 어려워 단위 테스트시 문제가 발생할 수 있다.
- 공유 변수를 잘못 설정하면 프로그램에 막대한 영향을 미칠 수 있다.
* 해결 방법 : 의존성 주입(DI)
싱글톤 패턴은 구현이 쉽고 많이 사용되지만 위처럼 모듈 간의 결합을 강하게 만들 수 있다는 단점이 있다. 이는 '의존성 주입(DI, Dependency Injection)'을 통해 모듈 간의 결합을 느슨하게 만들어 어느 정도 해결할 수 있다. 여기서 'A 객체가 B 객체에 의존성이 있다.' 라는 말을 코드로 표현하면 다음과 같다.
public class A {
private B b;
public A(B b) {
this.b = b;
}
}
public class B {
private String name;
public B(String name) {
this.name = name;
}
}
A 객체는 B 객체를 멤버 변수로 갖고 있다. 만약 B에 변경 사항이 생긴다면 A 또한 변해야 한다. 이처럼 특정 객체의 변경에 영향을 받을 때 A는 B에 의존한다고 한다. 스프링을 공부해본 사람이라면 알겠지만 스프링 컨테이너는 빈 객체들을 싱글톤을 관리한다. 의존성 주입을 통해 변경과 확장에 용이하다는 장점이 있다. 이처럼 메인 모듈(main module)이 직접 하위 모듈에 대한 의존성을 주기보다는, 중간에 의존성 주입자(dependency injector)가 간접적으로 의존성을 주입할 수 있다. 아래는 스프링 컨테이너를 사용한 의존성 주입 예시이다.
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
@RequiredArgsConstructor
@Service
public class OrderServiceImpl implements OrderService {
private final MemberService memberService;
...
}
위처럼 AppConfig에서 필요한 객체를 싱글톤으로 관리하여 반환하며, OrderService에선 이를 주입받아 사용하게 된다. 보다 자세한 내용은 아래 글을 확인하면 되겠다.
https://jnsodevelop.tistory.com/35
[Spring] Spring Container (스프링 컨테이너) - (1)
1️⃣ Spring Container (스프링 컨테이너) Spring 에서 'ApplicationContext' 는 '스프링 컨테이너' 라고 한다. 이는 DI, IoC 와도 관련이 있는데, 스프링 컨테이너를 통해 DI가 자동으로 되기 때문이다. 스프링
jnsodevelop.tistory.com
https://jnsodevelop.tistory.com/52
[Spring] Spring Container (스프링 컨테이너) - (2)
1️⃣ 개요 전에 스프링 컨테이너와 관련된 글을 올렸지만, 좀 더 깔끔하고 간결하게 중요한 내용만 다시 작성하였다. 따라서 아래 글을 읽고 온다면 조금 더 이해하기 쉬울 것이다. https://jnsodeve
jnsodevelop.tistory.com
4️⃣ 예시
- Spring Container 와 Servlet Container 모두 싱글톤 객체로 관리된다. 이는 객체 생성 비용을 효율적으로 관리해준다.
- 데이터베이스 연결 모듈