생성자가 여러 차례 호출되더라도 실제로 생성되는 객체는 하나이고 최초 생성 이후에 호출된 생성자는 최초의 생성자가 생성한 객체를 리턴
- 특징: 클래스 로딩 시점에 인스턴스를 생성
- 장점: 단순하고 thread-safe
- 단점: 인스턴스가 사용되지 않아도 메모리를 차지
public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance() {
return instance;
}
}- 특징: 인스턴스가 필요한 시점에 생성
- 장점: 필요할 때만 인스턴스 생성, 메모리 효율적
- 단점: Thread-safe하지 않음
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}- 특징: getInstance() 메서드에 synchronized 키워드 사용
- 장점: Thread-safe
- 단점: 성능 저하 (매번 동기화 처리)
public class SynchronizedSingleton {
private static SynchronizedSingleton instance;
private SynchronizedSingleton() {}
public static synchronized SynchronizedSingleton getInstance() {
if (instance == null) {
instance = new SynchronizedSingleton();
}
return instance;
}
}- 특징: 두 번 체크하여 동기화 영역 최소화
- 장점: Thread-safe하면서 성능 개선
- 단점: 복잡한 구현, Java 1.5 이전 버전에서 문제 발생 가능
public class DCLSingleton {
private static volatile DCLSingleton instance;
private DCLSingleton() {}
public static DCLSingleton getInstance() {
if (instance == null) {
synchronized (DCLSingleton.class) {
if (instance == null) {
instance = new DCLSingleton();
}
}
}
return instance;
}
}- 특징: 내부 정적 클래스를 사용하여 초기화
- 장점: Thread-safe, 성능 우수, 지연 초기화
- 단점: 복잡한 구현
public class DemandHolderSingleton {
private DemandHolderSingleton() {}
private static class SingletonHolder {
private static final DemandHolderSingleton INSTANCE = new DemandHolderSingleton();
}
public static DemandHolderSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}복잡한 객체의 생성 과정과 표현 방법을 분리하여 다양한 구성의 인스턴스를 만드는 생성 패턴
- 객체 생성 과정의 유연성: 같은 생성 과정으로 다양한 표현의 객체를 만들 수 있음
- 가독성 향상: 많은 매개변수를 가진 생성자 대신 메서드 체이닝을 통해 객체를 생성할 수 있음
- 불변성: 객체 생성 후 상태를 변경할 수 없게 만들 수 있음
@Builder어노테이션 사용 가능
객체가 할 수 있는 행위들 각각을 전략으로 만들어 놓고, 동적으로 행위의 수정이 필요한 경우 전략을 바꾸는 것만으로 행위의 수정이 가능하도록 만든 패턴
- 알고리즘 변경 쉬움
- 새로운 전략 추가하기 쉬움 (OCP)
- 조건문을 제거하여 더 깔끔한 코드
Context = 전략을 사용하는 클래스
Strategy Interface = 공통되는 interface
Concrete Strategy = Interface 를 구현한 class
실행될 기능을 캡슐화함으로써 주어진 여러 기능을 실행할 수 있는 재사용성이 높은 클래스를 설계하는 패턴
- 작업을 요청하는 객체와 그 작업을 수행하는 객체를 분리
- 새로운 Command를 추가하기 쉬움 (OCP)
- 복합 명령(매크로 명령)을 쉽게 구현할 수 있음
- 실행 취소(Undo) 기능을 구현하기 쉬움
Command = 실행될 기능에 대한 인터페이스
Concrete Command = 실제 실행될 기능을 구현
Invoker = 기능의 실행을 요청
Receiver = Concrete Command 에서 실제 기능 수행
일대다(one-to-many) 의존 관계를 정의하여, 어떤 객체의 상태가 변할 때 그와 연관된 객체 들에게 알림을 보내는 디자인 패턴
- 느슨한 결합(Loose Coupling): Subject와 Observer는 서로 독립적으로 변경 가능
- 기존 코드를 변경하지 않고 새로운 Observer를 추가 가능 (OCP)
- 일관성 있는 갱신: 모든 Observer가 동시에 갱신됨.
Subject = 관찰 대상이 되는 객체. Observer들을 등록하고 제거하는 메서드를 가짐
Observer = Subject의 변경을 통보받는 객체들의 인터페이스
ConcreteSubject = Subject 인터페이스를 구현하는 구체적인 클래스
ConcreteObserver = Observer 인터페이스를 구현하는 구체적인 클래스
객체에 동적으로 새로운 책임을 추가할 수 있게 해주는 디자인 패턴. 기존 코드를 변경하지 않고 확장할 수 있다.
- 단일 책임 원칙(SRP): 클래스가 하나의 책임만을 가짐
- 기존 코드를 수정하지 않고 새로운 기능을 추가 가능 (OCP)
- 런타임에 동적으로 기능을 추가/제거할 수 있음
Component = 원본 객체와 데코레이터에 공통적으로 정의되는 인터페이스
ConcreteComponent = 원본 객체. 추가적인 책임이 추가될 수 있는 기본 객체
Decorator = Component를 참조하며 공통 인터페이스를 구현하는 추상 클래스
ConcreteDecorator = 실제 책임을 추가하는 구체적인 데코레이터 클래스
알고리즘의 구조를 메서드에 정의하고, 하위 클래스에서 알고리즘의 특정 단계를 재정의할 수 있게 하는 디자인 패턴. 변하지 않는 기능은 상위 클래스에 만들어두고 자주 변경되며 확장할 기능은 하위 클래스에서 구현한다.
- 중복 코드 제거: 공통 알고리즘을 상위 클래스에 정의
- 하위 클래스에서 알고리즘 구조 변경 없이 특정 단계만 재정의 가능 (OCP)
- 코드 재사용과 유지보수성 향상: 변하지 않는 부분은 상위 클래스에서 관리
AbstractClass = 템플릿 메서드를 정의하는 추상 클래스. 알고리즘의 뼈대를 구현
ConcreteClass = 추상 클래스에서 정의한 추상 메서드를 구체적으로 구현하는 클래스
templateMethod() = 알고리즘의 골격을 정의하는 메서드. 하위 클래스에서 오버라이드 불가능(final)
primitiveOperation() = 하위 클래스에서 구현해야 하는 추상 메서드들
객체 생성을 위한 인터페이스를 정의하고, 인스턴스 생성은 하위 클래스가 결정하도록 하는 디자인 패턴. 객체의 생성과 사용을 분리한다.
- 객체 생성 로직과 사용 로직의 분리: 결합도 감소
- 기존 코드 수정 없이 새로운 제품 클래스 추가 가능 (OCP)
- 객체 생성의 캡슐화: 구체적인 객체 생성 과정을 감춤
Creator = 팩토리 메서드를 추상화한 클래스. 객체 생성을 위한 인터페이스를 정의
ConcreteCreator = 실제 객체를 생성하는 팩토리 메서드를 구현하는 클래스
Product = 팩토리 메서드로 생성될 객체의 공통 인터페이스
ConcreteProduct = 실제 생성되는 구체적인 제품 클래스
관련성 있는 여러 객체의 군을 생성하기 위한 인터페이스를 제공하는 디자인 패턴. 구체적인 클래스에 의존하지 않고 서로 연관되거나 의존적인 객체들의 조합을 만드는 인터페이스를 제공한다.
- 제품군을 쉽게 대체할 수 있음: 전체 제품군을 한번에 교체 가능
- 제품 사이의 일관성 보장: 같은 제품군의 객체들만 생성됨
- 구체적인 클래스와의 결합도를 낮춤 (DIP)
AbstractFactory = 연관된 객체들의 생성을 위한 인터페이스를 정의
ConcreteFactory = 구체적인 제품을 생성하는 팩토리 클래스
AbstractProduct = 연관된 제품들의 공통 인터페이스
ConcreteProduct = 구체적인 제품을 구현하는 클래스
Factory Method와의 주요 차이점은:
- Factory Method는 한 종류의 객체 생성을 다룸
- Abstract Factory는 연관된 여러 종류의 객체 군을 생성함
부분과 전체의 계층을 표현하기 위한 디자인 패턴. 클라이언트가 개별 객체와 복합 객체를 동일하게 다룰 수 있게 한다.
- 복잡한 트리 구조를 효과적으로 처리: 재귀적 구조를 통한 처리
- 단일 객체와 복합 객체를 동일하게 취급: 일관된 방식으로 처리 가능
- 새로운 요소 추가가 용이함 (OCP)
Component = Leaf와 Composite를 동일하게 다루기 위한 공통 인터페이스
Leaf = 구체적인 부분 클래스. 자식을 가질 수 없는 객체
Composite = 복합 객체. 부분 객체들을 포함하며 관리하는 클래스
Client = Component 인터페이스를 통해 복합 객체 구조를 조작
예를 들어 파일 시스템에서:
- Component = 파일시스템 요소
- Leaf = 파일
- Composite = 디렉토리 (다른 파일과 디렉토리를 포함할 수 있음)
한 클래스의 인터페이스를 클라이언트에서 사용하고자 하는 다른 인터페이스로 변환하는 디자인 패턴. 호환되지 않는 인터페이스를 가진 클래스들이 함께 동작하도록 해준다.
- 기존 코드를 변경하지 않고 새로운 인터페이스 적용 가능 (OCP)
- 호환되지 않는 클래스들을 함께 동작하게 함
- 기존 클래스의 재사용성을 높임
Target = 클라이언트가 사용하려는 인터페이스. 클라이언트가 직접 사용하는 인터페이스
Adaptee = 적응이 필요한 기존 클래스. 변환이 필요한 인터페이스를 가진 클래스
Adapter = Target 인터페이스를 구현하여 Adaptee의 인터페이스를 Target 인터페이스에 맞추는 클래스
Client = Target 인터페이스를 사용하는 클래스
두 가지 구현 방식:
- 클래스 어댑터: 상속을 사용
- 객체 어댑터: 합성을 사용
예시 상황:
- 220V를 사용하는 한국의 가전제품(Adaptee)
- 110V를 사용하는 미국의 전원(Target)
- 변압기(Adapter)가 220V를 110V로 변환








