0. Overview
강의를 듣던 중 디자인 패턴과 관련하여 공부를 하게 되었고, 디자인 패턴의 종류 중 중요하다고 생각되는 패턴들을 정리해보려고 합니다. 그 중 첫번째가 바로 어댑터 패턴 !
1. 어댑터 패턴 (Adapter Pattern)
- 현실 세계에서도 찾아보기 쉬운 패턴 (해외에서 한국 전자제품 사용하려면 110v '어댑터'가 필요합니다.)
- 호환되지 않는 인터페이스를 가진 객체들이 협업할 수 있도록 해주는 구조적 디자인 패턴
- 클라이언트가 사용하는 인터페이스를 따르지 않는 레거시 코드를 재사용할 수 있게 해준다.
1 - 1 다이어그램을 통한 어댑터 패턴의 관계
- Target : Adapter가 구현하는 인터페이스
- Adaptee : 어댑터 대상 객체 / 기존의 코드
- Adapter : Target Adaptee 중간에서 호환성이 없는 둘을 연결시켜주는 역할을 담당
- Client : Target 인터페이스를 만족하는 객체와 동작할 대상 (항상 Target 인터페이스만 사용합니다.)
1 - 2 어댑터 패턴의 장점과 단점
[ 장점 ]
- 기존 코드를 변경하지 않고 원하는 인터페이스 구현체를 만들어 재사용할 수 있다.
- 기존 코드를 손상시키지 않는 것은 객체지향 원칙 중 개방/폐쇄 원칙에 해당한다.
- 기존 코드가 하던 일과 특정 인터페이스 구현체로 변환하는 작업을 각기 다른 클래스로 분리하여 관리할 수 있다.
- 역할에 맞게 코드를 분리하는 것은 객체지향 원칙 중 단일 책임 원칙에 해당한다.
[ 단점 ]
- 다수의 새로운 인터페이스와 클래스를 도입해야 하므로 구조가 복잡해진다.
- 때로는 서비스 클래스를 변경하는 것이 더 간단할 때도 있다.
1 - 3 어댑터 패턴의 예시
간단한 예시 코드를 통해 어댑터 패턴을 이해해보겠습니다. 예를 들어, 미터법(미터, 센티미터)와 새로운 요구사항인 피트법(피트, 인치)간의 변환을 다루는 경우를 가정해볼게요. 이때 어댑터 패턴을 사용하여 이들을 통합할 수 있어요.
[ 미터법 클래스와 인터페이스 ]
public interface MeterSystem {
double getLengthInMeter();
}
public class MeterMeasurement implements MeterSystem {
private double lengthInMeter;
public MeterMeasurement(double lengthInMeter) {
this.lengthInMeter = lengthInMeter;
}
@Override
public double getLengthInMeter() {
return lengthInMeter;
}
}
[ 피트법 클래스 ]
public interface FeetSystem {
double getLengthInFeet();
}
public class FeetMeasurement implements FeetSystem {
private double lengthInFeet;
public FeetMeasurement(double lengthInFeet) {
this.lengthInFeet = lengthInFeet;
}
@Override
public double getLengthInFeet() {
return lengthInFeet;
}
}
[ Adapter 클래스를 사용하여 변환 ]
public class MeterToFeetAdapter implements FeetSystem {
private MeterSystem meterSystem;
public MeterToFeetAdapter(MeterSystem meterSystem) {
this.meterSystem = meterSystem;
}
@Override
public double getLengthInFeet() {
// 미터를 피트로 변환하는 로직
double lengthInMeter = meterSystem.getLengthInMeter();
double lengthInFeet = lengthInMeter * 3.28084; // 미터를 피트로 변환
return lengthInFeet;
}
}
이제 어댑터 패턴을 사용하여 기존의 "MeterSystem"을 피트법("FeetSystem")에 맞추는 것이 가능합니다.
[ 실행 ]
public class Main {
public static void main(String[] args) {
MeterSystem meterMeasurement = new MeterMeasurement(5.0);
FeetSystem feetMeasurement = new MeterToFeetAdapter(meterMeasurement);
double lengthInMeter = meterMeasurement.getLengthInMeter();
double lengthInFeet = feetMeasurement.getLengthInFeet();
System.out.println("길이(미터): " + lengthInMeter);
System.out.println("길이(피트): " + lengthInFeet);
}
}
이렇게 하면 어댑터 패턴을 사용해 서로 호환이 안되던 미터법 클래스와 피트법 클래스를 -> 기존의 미터법을 피트법으로 변환할 수 있으며 새로운 요구사항일 지원할 수 있게 됩니다. 변경된 요구사항에 대한 확장은 기존 코드를 변경하지 않고 새로운 어댑터 클래스를 추가함으로써 가능합니다.
추가적인 이해를 위해 다음과 같은 요구사항이 추가 되었다고 해보겠습니다.
또 다른 측정 시스템인 인치법으로도 변환할 수 있도록 추가 요청
[ 인치법 클래스와 인터페이스 ]
public interface InchSystem {
double getLengthInInches();
}
public class InchMeasurement implements InchSystem {
private double lengthInInches;
public InchMeasurement(double lengthInInches) {
this.lengthInInches = lengthInInches;
}
@Override
public double getLengthInInches() {
return lengthInInches;
}
}
[ 어댑터 클래스를 수정하여 인치법으로 변환 ]
public class MeterToFeetAndInchesAdapter implements InchSystem {
private MeterSystem meterSystem;
public MeterToFeetAndInchesAdapter(MeterSystem meterSystem) {
this.meterSystem = meterSystem;
}
@Override
public double getLengthInInches() {
// 미터를 피트와 인치로 변환하는 로직
double lengthInMeter = meterSystem.getLengthInMeter();
double lengthInFeet = lengthInMeter * 3.28084; // 미터를 피트로 변환
double lengthInInches = lengthInFeet * 12; // 피트를 인치로 변환
return lengthInInches;
}
}
이제 어댑터 패턴을 사용해 미터법을 피트법과 인치법으로 변환할 수 있습니다.
[ 실행 ]
public class Main {
public static void main(String[] args) {
MeterSystem meterMeasurement = new MeterMeasurement(5.0);
FeetSystem feetMeasurement = new MeterToFeetAdapter(meterMeasurement);
InchSystem inchMeasurement = new MeterToFeetAndInchesAdapter(meterMeasurement);
double lengthInMeter = meterMeasurement.getLengthInMeter();
double lengthInFeet = feetMeasurement.getLengthInFeet();
double lengthInInches = inchMeasurement.getLengthInInches();
System.out.println("길이(미터): " + lengthInMeter);
System.out.println("길이(피트): " + lengthInFeet);
System.out.println("길이(인치): " + lengthInInches);
}
}
추가 요구사항을 간단히 지원할 수 있게 되었습니다.
2. 자바에서의 어댑터 패턴
자바에서는 어댑터 패턴을 어떻게 활용하고 있을까요 ?
[ Collections 예제 ]
List<String> strings = Arrays.asList("a", "b", "c");
Enumeration<String> enumeration = Collections.enumeration(strings);
ArrayList<String> list = Collections.list(enumeration);
- 클라이언트가 간단한 문자열만 인자로 넘겨줘도 Collections 클래스를 생성할 수 있도록 도와줍니다.
- 둘째줄의 경우, strings가 Adaptee의 역할이며, Collections가 Adapter에 해당하는 역할이고, Enumeration이 Target에 해당하는 역할이에요.
<참고 자료 >