디자인 패턴의 핵심 원칙
열린/닫힌 원칙
기능 확장은 열려 있어야 하며, 기존 코드 수정은 피해야 합니다. 시스템 유지보수 시 기존 로직 변경 없이 새로운 기능을 추가할 수 있도록 설계하는 것이 중요합니다.
리스코프 대체 원칙
확장성 구현을 위한 핵심 방식으로, 상속 구조를 통해 기존 클래스를 재사용하는 방식입니다.
의존 역전 원칙
시스템의 유연성을 높이기 위해 인터페이스 기반 프로그래밍을 사용하는 기본 전제입니다.
인터페이스 격리 원칙
모듈 간 결합도를 낮추기 위해 특정 기능에 필요한 인터페이스만 제공하는 설계 접근법입니다.
팩토리 패턴 적용 사례
동일한 처리 로직이 반복되는 다양한 서비스 객체를 생성할 때 팩토리 패턴을 활용합니다. 클라이언트는 구체적인 구현을 알 필요 없이 추상화된 타입 이름만으로 객체를 생성할 수 있습니다.
@Service
@Slf4j
public class TypeFactory {
public TypeInterface createInstance(String targetType) {
String beanName = targetType + "Service";
try {
Object instance = ApplicationContextHelper.getBean(beanName);
return (TypeInterface)instance;
} catch (Exception e) {
log.error("타입 '{}'에 해당하는 서비스를 찾을 수 없습니다", targetType, e);
throw new IllegalStateException("서비스 생성 실패", e);
}
}
}
인터페이스 기반의 서비스 객체를 생성할 때는 스프링 컨테이너에서 직접 조회하여 사용할 수 있습니다.
스프링 컨텍스트 헬퍼 클래스
@Component
public class ApplicationContextHelper implements ApplicationContextAware {
private static ApplicationContext appContext;
@Override
public void setApplicationContext(ApplicationContext context) {
appContext = context;
}
public static <T> T getBean(String beanName, Class<T> requiredType) {
if (beanName == null || beanName.isEmpty()) {
throw new IllegalArgumentException("빈 이름이 유효하지 않습니다");
}
return appContext.getBean(beanName, requiredType);
}
}
서비스 인터페이스 정의
public interface CalculationService {
void execute();
}
구현체 예시
@Service
@Slf4j
public class BondService implements CalculationService {
@Override
public void execute() {
log.info("채권 계산 로직 실행");
}
}
컨트롤러 연동
@RestController
@RequestMapping("/api/v1/calculation")
public class CalculationController {
@Autowired
private TypeFactory factory;
@GetMapping("/process")
public ResponseEntity<String> process(@RequestParam String type) {
CalculationService service = factory.createInstance(type);
service.execute();
return ResponseEntity.ok("처리 완료");
}
}
싱글톤 패턴 구현 방식
지연 초기화 방식
클래스 로딩 시점이 아닌 실제 사용 시점에 인스턴스를 생성하는 방식입니다.
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
즉시 초기화 방식
클래스 로딩 시점에 인스턴스를 생성하고 유지하는 방식입니다.
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
지연 초기화 방식은 멀티스레드 환경에서 동기화 처리가 필요하며, 이로 인해 성능 저하가 발생할 수 있습니다. 반면 즉시 초기화 방식은 단순한 메서드 호출로 인스턴스를 얻을 수 있어 성능이 우수하지만, 메모리 소비량이 증가합니다. 일반적으로 스프링에서는 즉시 초기화 방식을 선호합니다.
프록시 패턴 활용
스프링 AOP와 같은 프레임워크에서 자주 사용되는 패턴입니다. 실제 개발 과정에서 구체적인 적용 사례는 제한적이지만, 관심사 분리(IoC)를 위한 핵심 개념으로 활용됩니다.