소프트웨어 엔지니어링에서 어댑터 패턴은 기존 클래스의 인터페이스를 다른 인터페이스로 사용할 수 있도록 하는 소프트웨어 디자인 패턴이다. 기존 클래스를 소스 코드를 수정하지 않고 다른 클래스와 함께 작동하도록 하는 데 자주 사용된다.
어댑터 디자인 패턴은 다음과 같은 문제를 해결한다.
- 클라이언트가 필요로 하는 인터페이스가 없는 클래스를 어떻게 재사용할 수 있나요?
- 호환되지 않는 인터페이스를 가진 클래스는 어떻게 함께 작동할 수 있는가?
- 클래스에 대체 인터페이스를 어떻게 제공할 수 있는가?
클라이언트가 필요로 하는 인터페이스가 없는 클래스를 어떻게 재사용할 수 있나요?
인터페이스가 클라이언트가 요구하는 인터페이스와 일치하지 않기 때문에 (이미 존재하는) 클래스를 재사용할 수 없는 경우가 종종 있다.
어댑터 디자인 패턴은 이러한 문제를 아래와 같이 해결한다.
- 클래스(어댑터)의 (호환되지 않는) 인터페이스를 클라이언트가 필요로 하는 다른 인터페이스(대상)로 변환하는 별도의 어댑터 클래스를 정의한다.
- 어댑터를 통해 필요한 인터페이스가 없는 클래스와 함께 작업(재사용)한다.
이 패턴의 핵심 아이디어는 (이미 존재하는) 클래스의 인터페이스를 변경하지 않고 별도의 어댑터를 통해 작업하는 것이다. 이때 클라이언트는 대상 클래스로 직접 작업하는지 아니면 대상 인터페이스가 없는 클래스가 있는 어댑터를 통해 작업하는지 알 수 없다.
사용 예시
@Override
public @Nullable Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
Assert.notNull(targetType, "Target type to convert to cannot be null");
if (sourceType == null) {
Assert.isTrue(source == null, "Source must be [null] if source type == [null]");
return handleResult(null, targetType, convertNullSource(null, targetType));
}
if (source != null && !sourceType.getObjectType().isInstance(source)) {
throw new IllegalArgumentException("Source to convert from must be an instance of [" +
sourceType + "]; instead it was a [" + source.getClass().getName() + "]");
}
GenericConverter converter = getConverter(sourceType, targetType);
if (converter != null) {
Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
return handleResult(sourceType, targetType, result);
}
return handleConverterNotFound(source, sourceType, targetType);
}
스프링의 GenericConversionService
의 convert
메서드에서는 GenericConverter
타입으로 컨버터를 사용하고 있다.
@Override
public void addConverter(Converter<?, ?> converter) {
ResolvableType[] typeInfo = getRequiredTypeInfo(converter.getClass(), Converter.class);
if (typeInfo == null && converter instanceof DecoratingProxy decoratingProxy) {
typeInfo = getRequiredTypeInfo(decoratingProxy.getDecoratedClass(), Converter.class);
}
if (typeInfo == null) {
throw new IllegalArgumentException("Unable to determine source type <S> and target type <T> for your " +
"Converter [" + converter.getClass().getName() + "]; does the class parameterize those types?");
}
addConverter(new ConverterAdapter(converter, typeInfo[0], typeInfo[1]));
}
@Override
public <S, T> void addConverter(Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter) {
addConverter(new ConverterAdapter(
converter, ResolvableType.forClass(sourceType), ResolvableType.forClass(targetType)));
}
@Override
public void addConverter(GenericConverter converter) {
this.converters.add(converter);
invalidateCache();
}
하지만 컨버터는 위와 같이 여러 가지 타입으로 등록할 수 있다. GenericConverter
타입으로 컨버터를 등록하면 별도의 처리 없이 컨버터를 등록할 수 있지만 Converter<S, T>
를 활용해 컨버터를 등록한다면 ConverterAdapter
를 활용해 타입을 맞춰주고 있다. ConverterAdapter
를 통해 Converter<S, T>
타입의 클래스를 GenericConverter
로 변환하여 컨버터를 사용하는 클라이언트가 GenericConverter
의 동일한 인터페이스만을 바라볼 수 있도록 유지할 수 있는 것이다.
@SuppressWarnings("unchecked")
private final class ConverterAdapter implements ConditionalGenericConverter {
// ...
}
public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}
ConverterAdapter
클래스는 GenericConverter
를 확장한 ConditionalGenericConverter
를 구현하고 있기에 GenericConversionService
에서 GenericConverter
로 등록되고 사용될 수 있다.
각 클래스의 추가 시점
참고로 Converter
클래스가 추가된 첫 커밋은 2009년 3월 7일, ConverterAdapter
클래스가 추가된 첫 커밋은 2009년 8월 4일이다.
public void addConverter(Converter<?, ?> converter) {
Class sourceType = typeInfo[0];
Class targetType = typeInfo[1];
// Adapter 적용 이전 구현
getSourceMap(sourceType).put(targetType, new ConverterGenericConverter(converter));
}
어댑터 패턴을 적용하기 이전에는 ConverterGenericConverter
를 사용하여 Converter
클래스의 등록을 구현하고 있었다.
'자바' 카테고리의 다른 글
Delegation Pattern 정리 (0) | 2025.04.14 |
---|---|
Decorator Pattern 정리 (0) | 2025.04.11 |
Class.isAssignableFrom(Class<?> var1) 정리 (0) | 2025.04.07 |
코틀린 receiver 이해를 위한 예시 정리 (0) | 2025.04.03 |
Function literals with receiver 문서 정리 (0) | 2025.04.02 |