Code

꼭 알아두어야 할 5가지 디자인 패턴: 모범 사례와 예제

스프링 디자인 패턴

안녕하세요! Devloo 입니다. 저는 자바 백엔드 개발자로서 Spring Boot와 Spring Framework를 10년 동안 사용하면서, 디자인 패턴이 견고하고 확장 가능한 애플리케이션을 구축하는 데 있어 얼마나 중요한 역할을 하는지 깨닫게 되었습니다. 이 글에서는 다섯 가지 필수 디자인 패턴을 탐구해 보고, 이를 효과적으로 Spring Boot 프로젝트에 적용하는 방법을 살펴보겠습니다. 각 패턴은 구현 예제와 함께 제공드리도록 하겠습니다. 🙂

Singleton Pattern (싱글톤 패턴)

Singleton Pattern (싱글톤 패턴)
Singleton Pattern (싱글톤 패턴), 이미지 출처 : refactoring.guru

싱글톤 패턴은 클래스의 인스턴스가 오직 하나만 존재하도록 보장하며, 이를 위한 전역 접근 지점을 제공합니다. 이는 데이터베이스 연결이나 캐싱 객체와 같은 자원을 관리하는 데 특히 유용합니다. Spring Boot에서 싱글톤 패턴을 구현하는

방법은 다음과 같습니다:

public class DatabaseConnection {
    private static volatile DatabaseConnection instance;

    private DatabaseConnection() {
        // 인스턴스 생성을 방지하기 위한 private 생성자
    }

    public static DatabaseConnection getInstance() {
        if (instance == null) {
            synchronized (DatabaseConnection.class) {
                if (instance == null) {
                    instance = new DatabaseConnection();
                }
            }
        }
        return instance;
    }
}

Spring의 Bean을 활용한 싱글턴의 구현은 아래 링크를 확인하세요:
https://www.baeldung.com/spring-boot-singleton-vs-beans#singleton-beans-in-spring

Factory Method Pattern (팩토리 메서드 패턴)

Factory Method Pattern (팩토리 메서드 패턴)
Factory Method Pattern (팩토리 메서드 패턴), 이미지 출처 : refactoring.guru

팩토리 메서드 패턴은 슈퍼클래스에서 객체를 생성하는 인터페이스를 제공하고, 서브클래스에서 생성할 객체의 타입을 변경할 수 있게 합니다. 이는 객체 생성 로직을 클라이언트 코드에서 분리하는 데 유용합니다.

Spring Boot에서의 예시는 다음과 같습니다:

/*
인터페이스 및 구현 클래스
*/
public interface PaymentProcessor {
    void processPayment();
}

@Component("creditCardProcessor")
public class CreditCardProcessor implements PaymentProcessor {
    @Override
    public void processPayment() {
        // 신용카드 결제 처리 로직
    }
}

@Component("paypalProcessor")
public class PayPalProcessor implements PaymentProcessor {
    @Override
    public void processPayment() {
        // PayPal 결제 처리 로직
    }
}

/*
팩토리 인터페이스 및 구현 클래스
*/
public interface PaymentProcessorFactory {
    PaymentProcessor createPaymentProcessor(String type);
}

@Component
public class PaymentProcessorFactoryImpl implements PaymentProcessorFactory {
    private final ApplicationContext context;

    @Autowired
    public PaymentProcessorFactoryImpl(ApplicationContext context) {
        this.context = context;
    }

    @Override
    public PaymentProcessor createPaymentProcessor(String type) {
        return context.getBean(type + "Processor", PaymentProcessor.class);
    }
}

/*
사용 예시
*/
@Service
public class PaymentService {
    private final PaymentProcessorFactory paymentProcessorFactory;

    public PaymentService(PaymentProcessorFactory paymentProcessorFactory) {
        this.paymentProcessorFactory = paymentProcessorFactory;
    }

    public void processPayment(String type) {
        PaymentProcessor processor = paymentProcessorFactory.createPaymentProcessor(type);
        processor.processPayment();
    }
}

Observer Pattern (옵저버 패턴)

Observer Pattern (옵저버 패턴)
Observer Pattern (옵저버 패턴), 이미지 출처 : refactoring.guru

옵저버 패턴은 객체 사이의 일대다 의존성을 정의하여, 하나의 객체 상태가 변경될 때 그 의존 객체들이 자동으로 통지되고 갱신되도록 합니다. 이는 이벤트 기반 시스템에서 자주 사용됩니다.

Spring Boot에서의 구현 방법은 다음과 같습니다:

import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Component
public class OrderListener implements ApplicationListener<OrderEvent> {
    @Override
    public void onApplicationEvent(OrderEvent event) {
        // 주문 이벤트 처리
    }
}

public class OrderEvent extends ApplicationEvent {
    public OrderEvent(Object source) {
        super(source);
    }
}

@Service
public class OrderService {
    private ApplicationEventPublisher eventPublisher;
    
    public OrderService(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }
    
    public void placeOrder() {
        // 주문 로직
        // 주문 이벤트 발행
        eventPublisher.publishEvent(new OrderEvent(this));
    }
}

Decorator Pattern (데코레이터 패턴)

Decorator Pattern (데코레이터 패턴)
Decorator Pattern (데코레이터 패턴), 이미지 출처 : refactoring.guru

데코레이터 패턴은 동일한 클래스의 다른 객체의 동작에 영향을 주지 않고, 객체에 동작을 동적으로 추가할 수 있게 합니다. 이는 로깅, 캐싱, 암호화 등의 기능을 기존 클래스에 추가하는 데 유용합니다.

Spring Boot에서의 구현 방법은 다음과 같습니다:

public interface DataService {
    void fetchData();
}

@Component
public class DataServiceImplementation implements DataService {
    @Override
    public void fetchData() {
        // 데이터 가져오기 구현
    }
}

@Component
public class LoggingDecorator implements DataService {
    private DataService delegate;
    
    public LoggingDecorator(DataService delegate) {
        this.delegate = delegate;
    }
    
    @Override
    public void fetchData() {
        // 데이터 가져오기 전에 로깅
        delegate.fetchData();
        // 데이터 가져온 후에 로깅
    }
}

Strategy Pattern (전략 패턴)

Strategy Pattern (전략 패턴), 이미지 출처 : refactoring.guru

전략 패턴은 알고리즘 군을 정의하고 각각을 캡슐화하여 상호 교환 가능하게 만드는 패턴입니다. 이는 여러 알고리즘을 상호 교환하여 사용할 수 있을 때 유용합니다.

Spring Boot에서의 구현 방법은 다음과 같습니다:

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import java.util.List;

// 압축 전략 인터페이스
public interface CompressionStrategy {
    void compress(String file);
    String getType();
}

// Zip 압축 전략 구현체
@Component
public class ZipCompressionStrategy implements CompressionStrategy {
    @Override
    public void compress(String file) {
        // Zip 압축 로직
        System.out.println("Compressing using ZIP strategy: " + file);
    }

    @Override
    public String getType() {
        return "zip";
    }
}

// RAR 압축 전략 구현체
@Component
public class RarCompressionStrategy implements CompressionStrategy {
    @Override
    public void compress(String file) {
        // RAR 압축 로직
        System.out.println("Compressing using RAR strategy: " + file);
    }

    @Override
    public String getType() {
        return "rar";
    }
}

// 압축 컨텍스트
@Component
public class CompressionContext {
    private final List<CompressionStrategy> strategies;

    // 여러 전략을 주입받음
    public CompressionContext(List<CompressionStrategy> strategies) {
        this.strategies = strategies;
    }

    // 파일 압축 메소드 (전략 설정 포함)
    public void compressFile(String file, String type) {
        CompressionStrategy strategy = strategies.stream()
                .filter(s -> type.equals(s.getType()))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("Strategy not found: " + type));
        strategy.compress(file);
    }
}

결론

디자인 패턴은 특히 Spring Boot와 같은 프레임워크를 사용할 때 모든 Java 백엔드 개발자가 반드시 갖춰야 할 지식입니다. 이러한 패턴을 숙달하고 프로젝트에 신중하게 적용하면 유지 보수성과 확장성이 뛰어나며 이해하기 쉽고 확장 가능한 코드를 작성할 수 있습니다.

이번 시간에는 다섯 가지 필수 디자인 패턴에 대해 알아보았습니다. 궁금하신 점은 댓글로 달아주세요 🙂
감사합니다 !! (_ _)

Written by 개발자서동우
안녕하세요! 저는 기술 분야에서 활동 중인 개발자 서동우입니다. 명품 플랫폼 (주)트렌비의 창업 멤버이자 CTO로 활동했으며, AI 기술회사 (주)헤드리스의 공동 창업자이자 CTO로서 역할을 수행했습니다. 다양한 스타트업에서 일하며 회사의 성장과 더불어 비즈니스 상황에 맞는 기술 선택, 개발팀 구성 및 문화 정착에 깊은 경험을 쌓았습니다. 개발 관련 고민은 언제든지 편하게 연락주세요 :) https://linktr.ee/dannyseo Profile

Leave a Reply

Your email address will not be published. Required fields are marked *