반응형
더보기
목차
- Introduction to Design Pattern
- Singleton Pattern
- Implementation
- Discussion
예상문제
- 코드 주어지고, context, problem, solution 서술
- Singleton 흐름
Introduction to Design Pattern
- 디자인 패턴이란?
- SW 를 설계할 때, 특정 맥락/ 상황에서 자주 발생하는 문제들의 해결 방법을 반복적으로 재사용할 수 있도록 패턴화한 것
- 다양한 설계 분야에서도 디자인 패턴의 개념이 사용됨
- 건축 분야에서 패턴에 대한 논의가 처음으로 시작됨
- Christopher Alexander– A pattern language, 1977
- 도시 환경을 설계하기 위한 ‘언어’ 를 설명하며 언어의 단위를 패턴으로 정리
- "각 패턴은 우리 주변에서 자주 반복해서 발생하는 문제와 그 문제를 해결하는 핵심을 기술해 동일한 일을 두 번 다시 하지 않고 해결할 수 있도록 한다."
- 바퀴를 다시 발명하지 마라 (Don't reinvent the wheel)
- 건축 분야에서 패턴에 대한 논의가 처음으로 시작됨
- 역사
- 1987, Ward Cunningham과 Kent Beck가 Alexander의 패턴 아이디어를 객체 지향 설계로 확장
- Smalltalk 언어를 기반으로 패턴을 설명
- “Using pattern languages for object-oriented programs”, OOPSLA-87
- 1987, Erich Gamma가 “important of patterns and how to capture them”에 대한 주제로 학위논문출간
- 1992, Jim Coplien이 C++언어 기반의 몇몇패턴을설명한 Erich “Advanced C++ Programming Styles and Idioms” 출간
- 1987, Ward Cunningham과 Kent Beck가 Alexander의 패턴 아이디어를 객체 지향 설계로 확장
- Design Patterns of GoF
- 1993-1994, design pattern에 대한 workshops이 진행됨
- Erich Gamma, Richard Hem, Ralph Johnson, John Vlissides에 의해 1994년 “Design Patterns: Elements of Reusable Object-Oriented Software”라는 책이 출간됨
- 위 저자를 Gang of Four (GoF)라고부르며,책에서는 23개의 패턴을 소개
- Erich Gamma, Richard Hem, Ralph Johnson, John Vlissides에 의해 1994년 “Design Patterns: Elements of Reusable Object-Oriented Software”라는 책이 출간됨
- 1993-1994, design pattern에 대한 workshops이 진행됨
- Why Patterns?
- 검증된 해결책
- 디자인 패턴은 SW 디자인의 일반적인 문제들에 대해 저명한 개발자에 의해 여러 시도되고 검증된 해결책을 모은 것
- 특정 문제에 대한 giant 들의 생각을 볼 수 있음 (lesson)
- 더 좋은 설계를 기반으로 견고하고 재사용성이 높은 코드를 만들 수 있음
- 디자인 패턴은 SW 디자인의 일반적인 문제들에 대해 저명한 개발자에 의해 여러 시도되고 검증된 해결책을 모은 것
- 팀원 간의 효율적인 의사소통
- 팀원들이 더 효율적으로 의사소통 하는데 사용할 수 있는 공통의 언어를 정의
- 객체를 하나만 생성하도록 제약하는 상황일 때
- 클래스의 생성자를 public 이 아닌 private 로 해서 외부에서 생성할 수 없도록 제약하고, 하나의 공유할 객체를 내부에서 ...., v.s.,
- Singleton 패턴을 쓰자!
- 검증된 해결책
- Warning about Design Patterns
- 디자인 패턴은 만병통치약이 아님
- 검증된 해결책이지만, 패턴이 미칠 영향과 결과를 주의 깊게 생각하지 않고 무작정 남용하는 것은 결과적으로 악영향
- 망치를 든 사람에겐 모든 게 못으로 보인다 (man-with-a-hammer syndrome)
- 패턴을 쓰지 않고 간단하게 해결할 수 있다면, 항상 간단한 것을 선택!
- 다른 모든 요소가 동일할 때 가장 단순한 설명이 최선 (Ockham's razor)
- 설계상의 문제에 적합하다는 확신이 들 때 패턴을 도입
- 이를 위해서는 맥락과 패턴을 정확하게 이해하고, 해당 맥락에 맞추어 사용해야 함
- 검증된 해결책이지만, 패턴이 미칠 영향과 결과를 주의 깊게 생각하지 않고 무작정 남용하는 것은 결과적으로 악영향
- 디자인 패턴은 만병통치약이 아님
- Categories of Design Patterns
- 생성 패턴 (creational design patterns)
- 객체 생성에 관련된 패턴
- 객체의 생성과 조합을 캡슐화해 특정 객체가 생성되거나 변경되어도 프로그램 구조에 영향을 크게 받지 않도록 유연성을 제공
- 구조 패턴 (structural design patterns)
- 클래스나 객체를 조합해 더 큰 구조를 만드는 패턴
ex) 서로 다른 인터페이스를 지닌 2개의 객체를 묶어 단일 인터페이스를 제공
- 클래스나 객체를 조합해 더 큰 구조를 만드는 패턴
- 행위 패턴 (behavior design patterns)
- 객체나 클래스 사이의 알고리즘이나 책임 분배에 관련된 패턴
- 한 객체가 수행할 수 없는 작업을 여러 개의 객체로 어떻게 분배할지, 그렇게 하면서도 객체 사이의 결합도를 최소화하는 것에 중점
- 생성 패턴 (creational design patterns)
- 구조
- 맥락 (Context)
- 문제가 발생하는 여러 상황을 기술
- 패턴이 적용될 수 있는 상황 (또는 패턴이 유용하지 못한 상황)
- 문제 (Problem)
- 패턴이 적용되어 해결될 필요가 있는 여러 디자인 이슈
- 여러 제약 사항과 영향력도 문제 해결을 위해 고려해야 함
- 해결 (Solution)
- 문제를 해결하도록 설계를 구성하는 요소들과 요소들 사이의 관계, 책임, 협력 관계를 기술 (필요하면 코드도 같이 살펴봄)
- 맥락 (Context)
- 디자인 패턴의 표현 (class diagram, sequential diagram, etc.)
Singleton Pattern
- 싱글톤 패턴이란?
- 하나의 클래스가 오직 하나의 인스턴스만 가질 수 있도록 하는 패턴
- singleton = 단 하나의 원소만 가지는 칩함 (e.g., {0})
- 생성 패턴에 속함
- 싱글톤 패턴이 사용되는 맥락
- 한 객체가 리소스를 많이 사용하는 경우 (생성 시 시간이 오래 걸리거나 메모리를 많이 쓰는 경우)
- 객체를 만들 때 마다 리소스를 객체 수에 비례해서 사용하게 됨
- 이런 경우 리소스 절약을 위해 하나만 만들고 (마치 전역변수 처럼) 공유해서 쓸 수 있을까?
- 싱글톤 패턴이 사용되는 예시
- DB 연결 (커넥션 풀), 파일, 스레드풀, 로깅 등등
- 하나의 클래스가 오직 하나의 인스턴스만 가질 수 있도록 하는 패턴
- 싱글톤 패턴의 표현
- 싱글톤 패턴은 단일 클래스에 대한 내용이기 때문에 클래스 다이어그램으로는 클래스 하나로 간단하게 표현됨
- 오직 하나의 인스턴스만 가져야하기 때문에 객체는 클래스 내부에서 만들어 공유해 사용하고 생성자는 클라이언트가 볼 수 없게 함
- 생성자가 private 로 지정해서 생성 제한
- static 으로 공유
- 클라이언트는 getInstance 메소드를 통해 인스턴스에 접근할 수 있음
- 오직 하나의 인스턴스만 가져야하기 때문에 객체는 클래스 내부에서 만들어 공유해 사용하고 생성자는 클라이언트가 볼 수 없게 함
- 표현은 간단하나 여러가지 구현 방법이 있음
- 싱글톤 패턴은 단일 클래스에 대한 내용이기 때문에 클래스 다이어그램으로는 클래스 하나로 간단하게 표현됨
- 싱글톤 패턴의 구현 방법
- 아래 방식은 모두 싱글톤 패턴을 지향하나 각각 장단점이 존재
- Lazy initialization
- Thread-safe initialization
- Eager initialization
- Double-checked locking
- Lazy holder (Bill Pugh's solution)
- Enum method
- 아래 방식은 모두 싱글톤 패턴을 지향하나 각각 장단점이 존재
Implementation
1. lazy initailization
public class Settings {
private static Settings instance;
private Settings() { }
public static Settings getInstance() {
if (instance == null) {
instance = new Settings();
}
return instance;
}
}
Settings settings = Settings.getInstance();
- 설명
- 멤버 변수 private static 선언
- 하나의 instance 로 공유, 외부에서 접근 X
- 생성자 private 선언
- 외부에서 인스턴스를 생성할 수 없어야 함
- getInstance 메소드 public static 선언
- global 하게 접근할 수 있어야 함
- if (instance == null) { instance = new Setting(); }
- 실제로 인스턴스를 써야하는 시점에 생성 (lazy)
- 객체가 사용되지 않고 있는 상황이라면 불필요하게 메모리를 차지하지 않음
- 멤버 변수 private static 선언
- 문제점
- 멀티 쓰레드 환경에서 인스턴스가 여러 존재 할 수 있음 (no thread-safe)
- 쓰레드는 리소스를 공유하고, 실행단위를 기억하면서 순차적으로 수행
- 예시 상황
public class Settings { private static Settings instance; private Settings() {} public static Settings getInstance() { if (instance == null) { instance = new Settings(); } return instance; } }
- Thread A 와 Thread B 가 수행
- A 가 if 문을 평가하고 내부로 진입 (아직 new 가 수행된 것은 아님)
- 그때 B 가 if 문을 평가함, 아직 A 가 생성하지 않았기 때문에 if 문이 true (내부 진입)
- 결과적으로 A와 B 둘다 if 문 내부로 들어왔기에 각자 객체를 생성
- 싱글톤 개념에 위배
2. Thread-safe initialization
- getInstance 메소드에 synchronized 키워드를 사용해서 한 번에 하나의 thread 만 들어오게 제약
- 장점 : thread-safe
- 단점 : synchronization 으로 인해 overhead 가 발생 (추가적인 성능 부하)
public static synchronized Setting getInstance() {
if(instance == null) {
instance = new Settings();
}
return instance;
}
3. Eager initialization
- 한 번 미리 상수 (constant) 로 만들어 두고 사용하는 방법
- static final 은 프로그램 로딩 시점에 만들어지기 때문에 thread-safe
- 객체 생성 비용이 적으면 괜찮지만, 크다면 당장 사용하지 않을 때는 공간을 차지하는 문제가 있음 (공간 자원 낭비 가능성)
public class Settings{
private static final Settings INSTANCE = new Settings();
private Settings(){}
public static Settings getInstance(){
return INSTANCE;
}
}
4. Double-checked locking
- Lazy 방식으로 생성하고 싶은데, 매번 동기화를 하지 않기 위해서, 최초 초기화 할 때만 동기화를 수행
- 장점 : lazy initialization 이 가능하고 동기화 부담이 적음
- 단점 : 코드 구성과 이해가 어려움 (JVM 1.5 이상부터 동작)
public class Settings{
private static volatile Settings instance;
private Settings() {}
public static Settings getInstance(){
if(instance == null) {
synchronized (Setting.class){
instance = new Settings();
}
}
return instance;
}
- 설명
- volatile 은 변수를 캐시가 아니라 메모리에서 읽어오도록 지정
- 이런게 있다고만 알고 있으면 된다.
- keyword 를 붙여줘야 정확하게 동작한다.
- volatile 은 변수를 캐시가 아니라 메모리에서 읽어오도록 지정
5. Lazy holder (Bill Pugh's solution)
- 클래스 안에 내부 static 클래스 (holder) 를 두는 방식
- 장점 : lazy initialization, thread-safe, 간결한 코드
- 단점 : 클라이언트가 임의로 싱글톤 패턴을 파괴할 수도 있음
- Reflection API, 직렬화/역직렬화 등
public class Settings{
private Settings() {}
public static class SettingsHolder{
private static final Settings INSTANCE = new Settings();
}
public static Settings getInstnace(){
return SettingsHolder.INSTANCE;
}
}
- 설명
- 내부 클래스 holder 는 static 이기에, Settings 가 초기화되어도 내부 클래스는 메모리에 로딩되지 않음
- final 로 지정함으로서 다시 값이 할당되지 않도록 방지
- getInstance() 를 호출하면, 이때 내부 클래스가 한번 초기화 되면서 객체를 최초 생성
6. Enum method
public enum Settings{
INSTANCE;
private Settings() {}
private static Settings getInstance(){
return INSTANCE;
}
public int getNumber(){
return number;
}
}
- 실제로 class 는 아니지만, class 처럼 구현
- 열거 타입에 활용되는 키워드지만, enum 의 특성을 응용한 사례
- 멤버를 만들 때 private 으로 만들고, 한번만 초기화 해 thread-safe
- 상수 뿐 아니라 변수/메소드 선언해 사용 가능 (싱글톤 클래스 처럼 응용)
- 코드가 간단하고 클라이언트가 임의 변경해 싱글톤을 깰 수 없음
- 단점
- 선언하는 순간 만들기 때문에 lazy 하지 않음
- 클래스 상속이 필요할 때 enum 은 상속 불가능
일반적으로 권장되는 싱글톤 구현 방법
- Lazy holder : 성능이 중요시 되는 환경
- lazy initialization 이 가능
- 그러나 ,클라이언트의 공격에 싱글톤이 깨질 여지가 있음
- Enum : 안전성이 중요시 되는 환경
- (살펴보지는 않았지만) reflection 및 직렬화 이슈에 자연스럽게 대응 가능
- 그러나 eager initialization 와 비슷하기 때문에 불필요하게 메모리를 차지할 수도 있음
Singleton 사용 사례
- Runtime (java.lang.Runtime)
- JAVA 프로그램이 실행 되고 있는 환경에 대한 객체
- 실행 환경 정보는 애플리케이션 전체에서 일관되게 유지되어야 하므로, 하나의 인스턴스만 존재해야 한다.
- 다수의 Runtime 인스턴스가 존재하면 JVM 상태를 중복 관리하거나 혼란을 초래할 수 있다.
public class RuntimeExample{ public static void main(String[] args){ Runtime runtime = Runtime.getRuntime(); System.out.println(runtime.maxMemory()); System.out.println(runtime.freeMemory()); } }
- JAVA 프로그램이 실행 되고 있는 환경에 대한 객체
- Logging (java.util.logging)
- 프로그램 로그를 남기기 위한 라이브러리
- 애플리케이션의 로그는 시스템 전반에서 수집되며, 이를 통합적으로 관리할 필요가 있다.
- 다수의 Logger 인스턴스를 생성하면 로그 메시지의 흐름이 분산되거나 일관성이 깨질 수 있다.
- 싱글톤 패턴을 사용하면 애플리케이션의 모든 로그를 중앙화된 하나의 Logger 인스턴스에서 처리할 수 있다.
import java.util.logging.Level; import java.util.logging.Logger; public class HelloWorld{ private static Logger logger = Logger.getLogger(HelloWorld.class.getName()); public static void main(String[] args){ logger.info("This is level info logging"); logger.log(Level.WARNING, "This is level warning logging"); logger.log(Level.SEVERE, "This is level severe logging"); System.out.println("Hello Java Logging APIs."); } }
- 프로그램 로그를 남기기 위한 라이브러리
Discussion
- 싱글톤 패턴의 문제점
- 모듈간 의존성 증가
- 여러 모듈들이 하나의 싱글톤 객체를 사용하니 싱글톤 객체 변경시 이를 참조하는 다른 모듈에도 영향이 감
- SOLID 원칙에 위배되는 사례가 많음
- SRP 위배 : 클래스 본연의 작업 책임 + 인스턴스 접근 관리 역할 책임
- OCP 위배 : 싱글톤은 무조건 단일 객체만을 생성하고 상속도 불가능
- DIP 위배 : 인터페이스가 아니라 싱글톤 객체와 의존 관계가 설정
- 단위 테스트가 어려움
- 단위 테스트는 서로 독립적이어야 하는데, 싱글톤은 마치 전역 변수와 같이 공유 되기 때문에 테스트 순서에 따라 테스트 결과에 종속이 생길 수 있음
- 많은 테스트 프레임워크들이 상속에 의존하는데 싱글톤은 상속을 할 수가 없음
- 모듈간 의존성 증가
- 싱글톤 패턴으로 얻을 수 있는 이득
- 클래스가 하나의 인스턴스만 가짐 (공유를 통해 리소스 절약)
- 싱글턴 객체는 처음 요청될 때만 초기화 (객체 생성 비용 절약)
- 해당 인스턴스에 대한 전역 접근을 할 수 있음 (전역 변수 처럼 사용)
- 정리
- 싱글톤 패턴은 한 개의 인스턴스를 보증하여 효율성을 확보할 수 있지만, 그에 따라 수반되는 여러 문제점이 있을 수 있음
- 이러한 문제점으로 인해 싱글톤 패턴은 유연성이 많이 떨어지는 패턴이라고 함 (안티 패턴)
- 해당 클래스의 객체가 무조건 한 개만 있어야 하는지, 여러 객체를 생성하면 효율에 악영향이 생기는지 등 꼭 필요한 상황인지 검토해야 함
- 싱글톤 패턴은 한 개의 인스턴스를 보증하여 효율성을 확보할 수 있지만, 그에 따라 수반되는 여러 문제점이 있을 수 있음
반응형
'3학년 2학기 학사 > 소프트웨어 분석 및 설계' 카테고리의 다른 글
[소프트웨어 분석 및 설계] L19. Facade Pattern, Flyweight Pattern, Proxy Pattern (0) | 2024.12.13 |
---|---|
[소프트웨어 분석 및 설계] L18. Composite Pattern, Decorator Pattern (0) | 2024.12.13 |
[소프트웨어 분석 및 설계] L17. Adapter Pattern, Bridge Pattern (3) | 2024.12.13 |
[소프트웨어 분석 및 설계] L16. Abstract Factory Pattern, Builder Pattern, Prototype Pattern (0) | 2024.12.13 |
[소프트웨어 분석 및 설계] L15. Factory Method (팩토리 메소드) (0) | 2024.12.12 |