특정한 기능을 모듈화
한 것을 의미getInstance메서드
로 모든 클라이언트에게 동일한 인스턴스를 반환private 생성자
를 가지는 특징을 가지며, 생성된 싱글톤 오브젝트는 저장할 수 있는 자신과 같은 타입의 스태틱 필드
를 정의리터럴{} 또는 new Object로 객체 생성
하면 다른 어떤 객체와도 같지 않기에 이 자체로 싱글톤 구현 가능 const obj ={
a : 27
}
const obj2 = {
a : 27
}
console.log(obj == obj2)
//false
class Singleton{
constructor(){
if(!Singleton.instance){
Singleton.instance = this
}
return Singleton.instance
}
getInstance(){
return this.instance
}
}
const a = new Singleton()
const b = new Singleton()
const URL = 'mongodb://localhost:27017/kundolapp'
const createConnection = url => ({"url" : url})
class DB {
constructor(url) {
if (!DB.instance) {
DB.instance = createConnection(url)
}
return DB.instance
}
connect() {
return this.instance
}
}
const a = new DB(URL)
const b = new DB(URL)
console.log(a === b) // true
데이터 베이스 연결 모듈
에 많이 쓰는 이유는 뭘까?싱글톤 패턴은 데이터베이스 연결을 관리하는 데에 많은 장점을 제공하지만, 남용할 경우 애플리케이션의 유지보수와 테스트가 어려워질 수 있다.
class Singleton {
private static class singleInstanceHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return singleInstanceHolder.INSTANCE;
}
}
public class HelloWorld{
public static void main(String []args){
Singleton a = Singleton.getInstance();
Singleton b = Singleton.getInstance();
System.out.println(a.hashCode());
System.out.println(b.hashCode());
if (a == b){
System.out.println(true);
}
}
}
/*
705927765
705927765
true
1. 클래스안에 클래스(Holder), static이며 중첩된 클래스인 singleInstanceHolder를
기반으로 객체를 선언했기 때문에 한 번만 로드되므로 싱글톤 클래스의 인스턴스는 애플리케이션 당 하나만 존재하며
클래스가 두 번 로드되지 않기 때문에 두 스레드가 동일한 JVM에서 2개의 인스턴스를 생성할 수 없습니다.
그렇기 때문에 동기화, 즉 synchronized를 신경쓰지 않아도 됩니다.
2. final 키워드를 통해서 read only 즉, 다시 값이 할당되지 않도록 했습니다.
3. 중첩클래스 Holder로 만들었기 때문에 싱글톤 클래스가 로드될 때 클래스가 메모리에 로드되지 않고
어떠한 모듈에서 getInstance()메서드가 호출할 때 싱글톤 객체를 최초로 생성 및 리턴하게 됩니다.
*/
하지만 싱글톤 패턴은 남용될 경우도 있으며,
객체간의 결합도가 높아질 수 있으므로
적절히 사용해야 한다. 또한, 멀티스레드 환경에서동기화
문제에 주의하여 구현해야 한다. 최근에는 Spring 프레임워크와 같은의존성 주입(Dependency Injection) 기술
을 통해 객체 관리를 수행하는 것이 더 권장되는 경향이 있다.
TDD(Test Driven Development)에 걸림돌이 된다.
- 주로 TDD시 단위 테스트를 주로 하는데 단위 테스트는 서로 독립적이어야 하며, 테스트를 어떤순서로든 실행할 수 있어야 하는데 독립적인 인스턴스 만들기가 어렵다.
- 의존 관계상 클라이언트가 구체 클래스에 의존
- private 생성자 때문에 테스트가 어렵다.
- 객체 인스턴스를 하나만 생성해서 공유하는 방식 때문에 싱글톤 객체를 stateful하게 설계 했을 경우 큰 장애 발생요인이 된다.
테스트 작성 (Test)
: 먼저, 새로운 기능 또는 기능의 변경을 위한 테스트 코드를 작성 이때 작성한 테스트는 아직 구현되지 않은 기능이므로 실패할 것테스트 실행 (Red)
: 작성한 테스트 코드를 실행 이 단계에서는 해당 테스트가 실패. 왜냐하면 아직 해당 기능이 구현되지 않았기 때문코드 구현 (Green)
: 실패한 테스트를 통과하기 위한 최소한의 코드를 구현한다. 이 단계에서는 테스트가 성공코드 리팩토링 (Refactor)
: 작성한 코드를 리팩토링하여 코드의 구조와 가독성을 개선이 단계에서도 테스트를 계속 통과하는지 확인모듈 간의 결합을 강하게 만들 수 있다는 단점
개념
2. 의존성 주입의 단점
UserService
클래스에서 UserRepository
인터페이스에 의존하는 경우UserService
클래스의 생성자를 통해 UserRepository
구현 클래스(UserRepositoryImpl
)를 주입받아 의존성을 해결UserService
클래스는 UserRepository
인터페이스의 구현 내용에 대해 알 필요가 없으며, 다른 구현체를 주입하여 쉽게 교체할 수 있다.// UserRepository.java
public interface UserRepository {
User findById(String userId);
void save(User user);
}
// UserService.java
public class UserService {
private UserRepository userRepository;
// 생성자를 통한 의존성 주입
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUserById(String userId) {
return userRepository.findById(userId);
}
public void saveUser(User user) {
userRepository.save(user);
}
}
// UserRepositoryImpl.java
public class UserRepositoryImpl implements UserRepository {
// 간단한 구현으로 예시를 들기 위해 생략
// 실제로는 데이터베이스 등에 접근하여 사용자 정보를 저장하고 조회하는 로직이 들어갈 수 있습니다.
}
// Main.java
public class Main {
public static void main(String[] args) {
// 의존성 주입을 통해 UserRepositoryImpl 인스턴스를 UserService에 주입
UserRepository userRepository = new UserRepositoryImpl();
UserService userService = new UserService(userRepository);
String userId = "exampleUserId";
User user = userService.getUserById(userId);
System.out.println("User ID: " + user.getUserId());
}
}