[면접을 위한 cs지식 ]디자인 패턴 싱글톤 패턴자료 구조/디자인 패턴2024. 10. 25. 10:50
Table of Contents
반응형
디자인 패턴과 프로그래밍 패러다임
라이브러리 vs 프레임워크
라이브러리
- 공통으로 사용될 수 있는 특정한 기능을 모듈화 한 것을 의미
- 폴더명, 파일명 등에 대한 규칙이 없고 프레임워크에 비해 자유롭다.
- 무언가를 자를 때 도구인 가위를 사용해서 내가 직접 컨트롤하는 것
프레임워크
- 공통으로 사용될 수 있는 특정한 기능들을 모듈화한 것을 의미
- 폴더명, 파일명 등에 대한 규칙이 있으며 라이브러리에 비해 좀 더 엄격하다.
- 다른 곳으로 이동할 때 도구인 비행기를 타고 이동하지만 비행기가 컨트롤 하는 것
싱글톤 패턴(singleton pattern)
- 하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴. 보통 데이터베이스 연결 모듈에 많이 사용된다.
- 전역 변수를 사용하지 않고 객체를 하나만 생성하도록 하며, 생성된 객체를 어디에서든지 참조할 수 있도록 하는 패턴
- 하나의 인스턴스만을 생성하며 getInstance메서드로 모든 클라이언트에게 동일한 인스턴스를 반환
- private 생성자를 가지는 특징을 가지며, 생성된 싱글톤 오브젝트는 저장할 수 있는 자신과 같은 타입의 스태틱 필드를 정의
1. 자바스크립트의 싱글톤 패턴
- 자바스크립트에서는 리터럴{} 또는 new Object로 객체 생성하면 다른 어떤 객체와도 같지 않기에 이 자체로 싱글톤 구현 가능
const obj ={
a : 27
}
const obj2 = {
a : 27
}
console.log(obj == obj2)
//false
- 원칙은 하나의 클래스에 하나의 인스턴스
- Singleton.instance라는 하나의 인스턴스를 가지는 클래스
class Singleton{
constructor(){
if(!Singleton.instance){
Singleton.instance = this
}
return Singleton.instance
}
getInstance(){
return this.instance
}
}
const a = new Singleton()
const b = new Singleton()
2. 데이터 베이스 연결 모듈
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
3. 그렇다면… 싱글톤 패턴을 데이터 베이스 연결 모듈에 많이 쓰는 이유는 뭘까?
- 리소스 절약
- 데이터베이스 연결은 비용이 많이 드는 작업 중 하나
- 매번 데이터베이스 연결을 열고 닫는 대신에 싱글톤 패턴을 사용하면 하나의 연결을 재사용하므로 리소스를 절약
- 성능 개선
- 매번 데이터베이스 연결을 새로 만드는 것보다 기존 연결을 재사용하는 것이 빠름
- 싱글톤 패턴을 사용하면 객체를 필요할 때마다 생성하는 비용을 줄여서 성능을 향상
- 데이터 일관성 유지
- 싱글톤 패턴을 사용하면 항상 하나의 연결만을 사용하므로 데이터 일관성을 보다 쉽게 유지할 수 있다.
- 중복 연결 방지
- 싱글톤 패턴을 사용하면 여러 곳에서 동시에 데이터베이스 연결을 생성하는 것을 방지할 수 있다.
- 애플리케이션 전체에서 하나의 연결을 공유할 수 있다.
- 스레드 안전성
- 멀티스레드 환경에서 데이터베이스 연결을 처리해야 할 때, 싱글톤 패턴을 사용하면 스레드 안정성을 보장
- 하나의 인스턴스만을 사용하므로 동시에 여러 스레드가 데이터베이스 연결에 접근하는 것을 방지
싱글톤 패턴은 데이터베이스 연결을 관리하는 데에 많은 장점을 제공하지만, 남용할 경우 애플리케이션의 유지보수와 테스트가 어려워질 수 있다.
4. 자바에서 싱글톤 패턴
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()메서드가 호출할 때 싱글톤 객체를 최초로 생성 및 리턴하게 됩니다.
*/
5. 자바에서는 왜 싱글톤 패턴을 쓸까?
- 리소스 절약
- 자원이 많이 소모되는 객체를 여러 개 생성하는 것보다 하나의 인스턴스를 재사용하는 것이 리소스를 절약
- 이는 메모리, CPU, 디스크 I/O 등 다양한 자원을 아낄 수 있다.
- 공유 데이터와 상태 유지
- 싱글톤 패턴을 사용하면 여러 객체 간에 동일한 상태를 공유할 수 있다.
- 따라서 여러 곳에서 같은 데이터를 사용해야 하는 경우 유용하게 사용될 수 있다.
- 설정 정보 관리
- 애플리케이션의 설정 정보를 싱글톤으로 유지하면, 여러 컴포넌트에서 해당 설정 정보에 접근할 수 있고, 변경이 필요한 경우 일관된 방식으로 관리할 수 있다.
- 데이터 일관성 유지
- 전역적인 접근 가능성
- 스레드 안정성
- 멀티스레드 환경에서 싱글톤 패턴을 구현하면 스레드 안정성을 보장
- 여러 스레드에서 동시에 객체를 생성하는 것을 방지하여 충돌을 예방
하지만 싱글톤 패턴은 남용될 경우도 있으며, 객체간의 결합도가 높아질 수 있으므로 적절히 사용해야 한다. 또한, 멀티스레드 환경에서 동기화 문제에 주의하여 구현해야 한다. 최근에는 Spring 프레임워크와 같은 의존성 주입(Dependency Injection) 기술을 통해 객체 관리를 수행하는 것이 더 권장되는 경향이 있다.
6. 그렇다면 단점은..?
TDD(Test Driven Development)에 걸림돌이 된다.
- 주로 TDD시 단위 테스트를 주로 하는데 단위 테스트는 서로 독립적이어야 하며, 테스트를 어떤순서로든 실행할 수 있어야 하는데 독립적인 인스턴스 만들기가 어렵다.
- 의존 관계상 클라이언트가 구체 클래스에 의존
- private 생성자 때문에 테스트가 어렵다.
- 객체 인스턴스를 하나만 생성해서 공유하는 방식 때문에 싱글톤 객체를 stateful하게 설계 했을 경우 큰 장애 발생요인이 된다.
7. 이를 해결하기 위해서는 무상태(stateless)로 설계
- 특정 클라이언트에 의존적인 필드가 있으면 안됨
- 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안됨
- 가급적 읽기 전용으로 만들고, 필드 대신에 자바에서 공유되지 않는 지역변수, 파라미터, ThreadLocal 등을 사용
✋여기서 잠깐? TDD(Test Driven Development)란?
- 소프트웨어 개발 방법론 중의 하나로, 소프트웨어를 개발할 때 테스트 코드를 먼저 작성하고, 그 테스트 코드를 통과하는 코드를 구현하는 접근 방식
- 테스트 코드의 단계
- 테스트 작성 (Test): 먼저, 새로운 기능 또는 기능의 변경을 위한 테스트 코드를 작성 이때 작성한 테스트는 아직 구현되지 않은 기능이므로 실패할 것
- 테스트 실행 (Red): 작성한 테스트 코드를 실행 이 단계에서는 해당 테스트가 실패. 왜냐하면 아직 해당 기능이 구현되지 않았기 때문
- 코드 구현 (Green) : 실패한 테스트를 통과하기 위한 최소한의 코드를 구현한다. 이 단계에서는 테스트가 성공
- 코드 리팩토링 (Refactor): 작성한 코드를 리팩토링하여 코드의 구조와 가독성을 개선이 단계에서도 테스트를 계속 통과하는지 확인
- TDD의 핵심 아이디어
- 테스트를 먼저 작성함으로써 요구사항을 명확하게 이해하고, 정확한 코드를 작성하도록 유도하는 것
- 또한, 테스트를 작성함으로써 변경 사항이 코드에 미치는 영향을 빠르게 확인하고, 버그를 조기에 발견하여 개선할 수 있다.
- TDD의 장점
- 안정성 향상
- 코드 품질 개선
- 유지보수 용이성:
- 개발과 테스트의 지속적인 통합
- 문서화
- 단점
- 초기 투자
8. 의존성 주입(DI, Dependency Injection)
- 싱글톤 패턴은 모듈 간의 결합을 강하게 만들 수 있다는 단점
- 이때 의존성 주입(DI, Dependency Injection)을 통해 모듈 간의 결합을 좀 더 느슨하게 만들어 해결 가능
- 의존성 = 종속성 ⇒ A가 B에 의존성이 있다는 것은 B의 변경 사항에 대해 A 또한 변해야 한다.
- 개념
- 객체 간의 의존 관계를 코드 내에서 명시적으로 설정하는 것이 아니라, 외부에서 의존하는 객체를 주입하는 방식
- 메인 모듈이 직접 다른 하위 모듈에 대한 의존성을 주기보다는 중간에 의존성 주입자가 이 부분을 가로채 메인모듈이 간접적으로 의존성 주입하는 방법
- 객체 간의 결합도를 낮추고 유연하고 테스트하기 쉬운 코드를 작성
2. 의존성 주입의 단점
- 모듈들이 더욱 더 분리되므로 클래스 수가 늘어나 복잡성 증가, 약간의 런타임 패널티
3. 의존성 주입 원칙 - 상위 모듈은 하위 모듈에서 어떤 것도 가져오면 않아야 한다.
- 둘 다 추상화에 의존해야 하며, 이 때 추상화는 세부 사항에 의존하지 말아야 한다.
9. 의존성 주입의 예제
- UserService 클래스에서 UserRepository 인터페이스에 의존하는 경우
- UserService 클래스의 생성자를 통해 UserRepository 구현 클래스(UserRepositoryImpl)를 주입받아 의존성을 해결
- 이렇게 하면 UserService 클래스는 UserRepository 인터페이스의 구현 내용에 대해 알 필요가 없으며, 다른 구현체를 주입하여 쉽게 교체할 수 있다.
- 이로 인해 코드 유지보수와 테스트가 용이해지는 장점
- UserRepository 인터페이스
// UserRepository.java
public interface UserRepository {
User findById(String userId);
void save(User user);
}
- UserService 클래스
// 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);
}
}
- UserRepository 구현 클래스
// UserRepositoryImpl.java
public class UserRepositoryImpl implements UserRepository {
// 간단한 구현으로 예시를 들기 위해 생략
// 실제로는 데이터베이스 등에 접근하여 사용자 정보를 저장하고 조회하는 로직이 들어갈 수 있습니다.
}
- Main 메서드 ⇒ 메인 모듈
// 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());
}
}
반응형
'자료 구조 > 디자인 패턴' 카테고리의 다른 글
[면접을 위한 cs지식 ]디자인 패턴 MVVM 패턴 (5) | 2024.11.07 |
---|---|
[면접을 위한 cs지식 ]디자인 패턴 노출모듈 패턴 (0) | 2024.10.25 |
@mellona :: 주니어의 다사다난 성장기
안녕하세요. si 회사 소속 sm LMS 팀에 소속중인 1년차 백엔드 개발자입니다😀 함께 나누고 성장하는 것을 좋아해요. 언제든 디스코드나 구글 메일로 질문해도 됩니다!
⭐ 잘못된 내용은 댓글 적어주세요 :)