실무에서 활용하는 디자인 패턴
디자인 패턴은 소프트웨어 설계에서 반복적으로 발생하는 문제를 해결하기 위한 검증된 솔루션입니다. Java, Spring Boot, SQL을 활용한 프로젝트 경험을 통해 적절한 디자인 패턴을 적용함으로써 코드의 유지보수성, 가독성, 확장성을 크게 향상시킬 수 있었습니다. 이 글에서는 실무에서 사용한 주요 디자인 패턴과 그 실질적인 이점을 살펴보겠습니다.
1. 생성 패턴 (Creational Patterns)
생성 패턴은 객체 생성 메커니즘에 중점을 두며, 객체 생성 과정을 최적화하고 시스템을 유연하고 느슨하게 결합할 수 있도록 합니다.
a. 싱글톤 패턴 (Singleton Pattern)
목적: 클래스의 인스턴스를 하나만 생성하고 이를 전역적으로 접근할 수 있도록 보장합니다.
실무 적용 사례: 프로젝트에서 자주 사용되는 설정 값을 관리하는 공유 설정 서비스에 싱글톤 패턴을 적용했습니다. 이를 통해 설정 데이터를 한 번만 로드하고 여러 모듈에서 재사용할 수 있어 메모리 사용을 줄이고 효율성을 높일 수 있었습니다.
코드 예제:
public class ConfigurationService {
private static ConfigurationService instance;
private Properties properties;
private ConfigurationService() {
properties = new Properties();
loadProperties();
}
public static synchronized ConfigurationService getInstance() {
if (instance == null) {
instance = new ConfigurationService();
}
return instance;
}
public String getProperty(String key) {
return properties.getProperty(key);
}
}
b. 팩토리 메서드 패턴 (Factory Method Pattern)
목적: 객체 생성 인터페이스를 정의하되, 어떤 객체를 생성할지의 결정은 서브클래스에서 내리도록 합니다.
실무 적용 사례: 작업 매핑 기능 개발 시 팩토리 메서드 패턴을 사용하여 다양한 작업 엔티티를 처리했습니다. 이를 통해 새로운 작업 유형이 추가될 때 기존 코드를 수정하지 않고도 쉽게 확장할 수 있었습니다. 이는 개방/폐쇄 원칙(Open/Closed Principle)을 준수하는 방식입니다.
코드 예제:
public abstract class Task {
public abstract void execute();
}
public class LawComparisonTask extends Task {
@Override
public void execute() {
// 특정 구현
}
}
public class TaskFactory {
public static Task getTask(String taskType) {
switch (taskType) {
case "LawComparison":
return new LawComparisonTask();
default:
throw new IllegalArgumentException("알 수 없는 작업 유형입니다.");
}
}
}
2. 구조 패턴 (Structural Patterns)
구조 패턴은 클래스와 객체의 구성 방식을 다루며, 시스템을 효율적이고 유연하게 유지합니다.
a. 어댑터 패턴 (Adapter Pattern)
목적: 클래스의 인터페이스를 클라이언트가 기대하는 다른 인터페이스로 변환합니다.
실무 적용 사례: 기존 레거시 시스템을 새로운 플랫폼에 통합할 때 어댑터 패턴을 사용했습니다. 레거시 시스템이 다른 인터페이스를 노출하고 있었기 때문에 핵심 로직을 다시 작성하는 대신, 어댑터를 만들어 인터페이스 차이를 해결했습니다.
코드 예제:
public interface NewLawService {
void processLawData(String data);
}
public class LegacyLawService {
public void execute(String lawData) {
// 레거시 구현
}
}
public class LawServiceAdapter implements NewLawService {
private LegacyLawService legacyLawService;
public LawServiceAdapter(LegacyLawService legacyLawService) {
this.legacyLawService = legacyLawService;
}
@Override
public void processLawData(String data) {
legacyLawService.execute(data);
}
}
3. 행위 패턴 (Behavioral Patterns)
행위 패턴은 객체 간의 소통 방식과 상호작용 방법을 정의하여 책임을 분산시킵니다.
a. 전략 패턴 (Strategy Pattern)
목적: 알고리즘군을 정의하고 각각을 캡슐화하여 교체 가능하도록 합니다.
실무 적용 사례: 다양한 작업 매핑을 처리하는 과정에서 전략 패턴을 활용해 작업 유형에 따라 동적으로 처리 알고리즘을 선택하고 실행했습니다.
코드 예제:
public interface TaskProcessingStrategy {
void process();
}
public class ComparisonStrategy implements TaskProcessingStrategy {
@Override
public void process() {
// 비교 작업에 대한 로직
}
}
public class TaskProcessor {
private TaskProcessingStrategy strategy;
public TaskProcessor(TaskProcessingStrategy strategy) {
this.strategy = strategy;
}
public void execute() {
strategy.process();
}
}
4. 내가 고민하는 디자인 패턴은 무엇일까?
서비스 계층 분리와 인터페이스 활용에 대한 고민
최근 프로젝트에서 메서드의 설계 방식을 두고 내부적으로 많은 고민을 했습니다. 이 메서드는 하이라이트,주석 업데이트하는 기능을 담당하고 있습니다. 하지만 문제는 해당 메서드에서 주석에 대해 별도의 서비스 클래스와 인터페이스가 존재함에도 불구하고 주석 매퍼를 직접 호출하고 있다는 점이었습니다.
다른 개발자는 단순한 매퍼 업데이트 작업이기 때문에 굳이 인터페이스를 통해 해시맵을 전달하고 처리하는 것이 비효율적이라고 생각했지만, 저는 각 테이블마다 처리 로직이 다르므로 인터페이스를 분리하는 것이 더 적절하다고 판단했습니다.
어떤 접근이 더 좋은 디자인 패턴일까?
1. 서비스 계층을 통한 접근이 바람직합니다.
- 캡슐화: 각 서비스는 자신의 도메인 로직과 데이터 접근을 캡슐화해야 합니다.
- 모듈성: 서비스 간의 결합도를 낮추어 시스템의 복잡성을 줄이고 유지보수를 쉽게 합니다.
- 재사용성과 테스트 용이성: 서비스 메서드를 통해 접근하면 모의 객체(mock)를 활용한 단위 테스트가 용이해집니다.
2. 매퍼를 직접 호출하는 것은 권장되지 않습니다.
- 높은 결합도: 한 서비스에서 다른 도메인의 매퍼를 직접 호출하면 두 서비스 간의 결합도가 높아집니다.
- 유지보수 어려움: 데이터베이스 스키마나 매퍼 로직이 변경되면 이를 호출하는 모든 서비스 코드를 수정해야 합니다.
- 관심사 혼합: 한 서비스에서 여러 도메인의 로직을 처리하게 되면 코드의 복잡도가 증가하고 가독성이 떨어집니다.
예시 코드 수정:
결론
디자인 패턴을 고민하고 적용하는 과정은 단순한 코딩을 넘어 소프트웨어의 품질을 향상시키는 중요한 과정입니다. 인터페이스 분리와 서비스 계층을 활용하는 방식은 코드의 모듈성과 재사용성을 높이고, 유지보수를 쉽게 만듭니다. 이런 고민과 적용 과정이 결국 좋은 소프트웨어 설계를 만들어가는 길이라고 생각합니다.
'프로그래밍 언어 > Java' 카테고리의 다른 글
[Java] 싱글톤(Singleton) 패턴의 사용 이유와 문제점 (1) | 2025.01.27 |
---|---|
[Java] 멀티스레딩 및 동시성 (1) | 2025.01.05 |
[Java] 자바 최신 문법 (Java 17+) (1) | 2025.01.05 |
[Java] Garbage Collection(가비지 컬렉션)의 개념 및 동작 원리 (2) | 2024.09.20 |
[JAVA] 자바에서 Comparable과 Comparator 객체 비교의 이해와 활용 (0) | 2024.06.11 |