⚙️ 디자인 패턴
디자인 패턴은 소프트웨어 개발에서 자주 발생하는 문제를 해결하기 위한 재사용 가능한 설계 템플릿입니다. 코드의 유지보수성과 확장성을 높이고, 협업 시 의사소통을 원활하게 하는 중요한 개념입니다. 이번 강의에서는 대표적인 생성 패턴, 구조 패턴, 행동 패턴을 깊이 있게 다룹니다. 각 패턴은 서로 다른 문제를 해결하며, 적절한 상황에 맞게 적용할 수 있도록 설계되었습니다.
📌 목차
- 생성 패턴 (Singleton, Factory)
- 구조 패턴 (Adapter, Composite)
- 행동 패턴 (Strategy, Observer)
🔹 1. 생성 패턴
생성 패턴은 객체 생성 방식을 체계적으로 관리하여 시스템의 복잡성을 줄이고 유연성을 높이는 설계 패턴입니다. 대규모 애플리케이션에서는 객체 생성을 일관성 있게 제어하는 것이 매우 중요합니다.
🖥️ Singleton 패턴
Singleton 패턴은 애플리케이션에서 클래스의 인스턴스를 하나만 생성하고, 어디서든 이 인스턴스에 접근할 수 있도록 보장합니다. 주로 설정 정보, 로깅, 리소스 풀 관리 등에 활용됩니다. 멀티스레드 환경에서 안전하게 인스턴스를 생성하도록 getInstance() 메서드에 동기화를 적용합니다.
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
위 코드에서 생성자가 private로 선언되어 외부에서 직접 객체를 생성할 수 없으며, getInstance() 메서드를 통해서만 단 하나의 인스턴스를 얻을 수 있습니다.
💻 Factory 패턴
Factory 패턴은 객체 생성 로직을 별도의 팩토리 클래스에 캡슐화하여 클라이언트 코드가 구체적인 클래스에 의존하지 않도록 합니다. 이를 통해 객체 생성 과정을 분리하고, 향후 객체 종류가 추가되거나 변경될 때 코드 수정 범위를 최소화할 수 있습니다.
interface Product {
void use();
}
class ConcreteProduct implements Product {
public void use() {
System.out.println("ConcreteProduct 사용");
}
}
class ProductFactory {
public Product createProduct() {
return new ConcreteProduct();
}
}
팩토리 메서드는 반환 타입을 인터페이스나 상위 클래스로 지정하여 클라이언트가 구체적인 구현에 의존하지 않도록 합니다. 이는 유연한 확장성을 지원하는 중요한 디자인 원칙입니다.
🔹 2. 구조 패턴
구조 패턴은 클래스나 객체 간의 관계를 다루어 더 큰 구조를 구성할 때 유용한 패턴입니다. 객체 간 인터페이스를 변환하거나, 복합 객체를 단일 객체처럼 다룰 수 있게 하여 시스템을 모듈화 하고 재사용성을 높입니다.
🖥️ Adapter 패턴
Adapter 패턴은 호환되지 않는 인터페이스를 가진 클래스들이 함께 작동하도록 중간에서 인터페이스를 변환해 주는 역할을 합니다. 주로 레거시 코드와 신기능을 통합할 때 많이 사용됩니다.
interface Target {
void request();
}
class Adaptee {
public void specificRequest() {
System.out.println("특정 요청");
}
}
class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
adaptee.specificRequest();
}
}
Adapter 클래스는 기존의 Adaptee 객체를 감싸 Target 인터페이스에 맞게 변환하여 클라이언트가 일관된 방식으로 호출할 수 있도록 합니다.
💻 Composite 패턴
Composite 패턴은 객체를 트리 구조로 구성하여 단일 객체(Leaf)와 복합 객체(Composite)를 동일하게 다룰 수 있게 합니다. 복잡한 계층 구조를 간단하게 표현하고 처리할 수 있어 GUI, 파일 시스템, 문서 편집기 등에서 자주 쓰입니다.
import java.util.ArrayList;
import java.util.List;
interface Component {
void operation();
}
class Leaf implements Component {
public void operation() {
System.out.println("Leaf operation");
}
}
class Composite implements Component {
private List children = new ArrayList<>();
public void add(Component child) {
children.add(child);
}
public void operation() {
for (Component child : children) {
child.operation();
}
}
}
Composite 객체는 자식 객체를 관리하며, 자식들이 모두 동일한 인터페이스를 구현하므로 클라이언트는 단일 객체든 복합 객체든 동일하게 처리할 수 있습니다. 이를 통해 복잡한 객체 구조를 단순화합니다.
🔹 3. 행동 패턴
행동 패턴은 객체 간의 상호작용과 책임 분배에 중점을 둡니다. 객체들이 효과적으로 소통하고 협력할 수 있도록 구조를 정의하며, 유연한 변경과 확장이 가능합니다.
🖥️ Strategy 패턴
Strategy 패턴은 알고리즘 군을 캡슐화하여 각각을 독립적으로 정의하고, 필요에 따라 동적으로 알고리즘을 변경할 수 있게 합니다. 이는 if-else나 switch문으로 여러 조건을 처리하는 대신 객체 지향적으로 문제를 해결합니다.
interface Strategy {
void execute();
}
class ConcreteStrategyA implements Strategy {
public void execute() {
System.out.println("Strategy A 실행");
}
}
class ConcreteStrategyB implements Strategy {
public void execute() {
System.out.println("Strategy B 실행");
}
}
class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public void executeStrategy() {
strategy.execute();
}
}
Context 클래스는 Strategy 인터페이스 타입의 객체를 보유하며, 클라이언트는 실행 중에 setStrategy() 메서드를 통해 원하는 전략을 바꿀 수 있습니다. 이는 코드의 유연성과 확장성을 극대화합니다.
💻 Observer 패턴
Observer 패턴은 한 객체의 상태 변화가 있을 때 그 객체에 의존하는 여러 객체들에게 자동으로 알림을 전달하는 패턴입니다. 이를 통해 객체 간 느슨한 결합을 유지하면서 상태 변화를 효율적으로 처리할 수 있습니다.
import java.util.ArrayList;
import java.util.List;
interface Observer {
void update(String message);
}
class ConcreteObserver implements Observer {
private String name;
public ConcreteObserver(String name) {
this.name = name;
}
public void update(String message) {
System.out.println(name + " received: " + message);
}
}
class Subject {
private List observers = new ArrayList<>();
public void addObserver(Observer observer) {
observers.add(observer);
}
public void removeObserver(Observer observer) {
observers.remove(observer);
}
public void notifyObservers(String message) {
for (Observer observer : observers) {
observer.update(message);
}
}
}
Subject 객체는 상태 변화를 감지하면 등록된 모든 Observer에게 알림을 전달합니다. Observer는 독립적으로 동작하며, 필요한 경우 추가하거나 제거할 수 있어 유연한 이벤트 처리 구조를 만듭니다.
이번 강의에서 살펴본 디자인 패턴들은 개발자가 코드의 품질을 높이고, 유지보수와 확장을 쉽게 할 수 있도록 돕는 핵심 도구입니다. 패턴을 정확히 이해하고 적절히 활용하면 프로젝트의 성공 가능성을 크게 높일 수 있습니다. 앞으로 다양한 상황에서 이들 패턴을 응용해 보시길 권장합니다.
'Programming' 카테고리의 다른 글
| JAVA Stream & 람다식 (46) | 2025.11.28 |
|---|---|
| JAVA 리플렉션과 어노테이션 (63) | 2025.11.27 |
| JAVA 네트워크 프로그래밍 (43) | 2025.11.25 |
| JAVA 쓰레드와 동시성 (62) | 2025.11.24 |
| JAVA 클래스 고급 (40) | 2025.11.23 |