참조 문서
https://yozm.wishket.com/magazine/detail/2077/
어댑터 패턴
어댑터 패턴은 한 클래스의 인터페이스를 클라이언트가 기대하는 다른 인터페이스로 변환하는 패턴입니다. 이를 통해 인터페이스 호환성 문제를 해결할 수 있습니다. (구조패턴)
1) 어댑터 패턴 정의
어댑터 패턴(Adapter Pattern)은 호환되지 않는 인터페이스들을 연결하는 디자인 패턴을 말합니다. 이 패턴은 기존의 클래스를 수정하지 않고도, 특정 인터페이스를 필요로 하는 코드에서 사용할 수 있게 해줍니다. 또한 클래스의 인터페이스를 다른 인터페이스로 변환할 수도 있는데요. 이를 통해 서로 다른 인터페이스를 가진 클래스들이 상호 작용할 수 있도록 해서 코드의 재사용성을 증대시키게 됩니다.
2) 어댑터 패턴의 주요 구성 요소
어댑터 패턴 주요 구성 요소로는 타겟(Target), 어댑티(Adaptee), 어댑터(Adapter), 클라이언트(Client) 이렇게 4가지가 있습니다. 타겟은 클라이언트가 직접적으로 호출하는 인터페이스를 말하며, 어댑티는 아직 호환되지 않은 기존 클래스(또는 인터페이스)를 의미합니다. 클라이언트는 특정 작업을 요청하는 클래스를 말하며, 어댑터는 타겟 인터페이스를 구현하여 클라이언트 요청을 애댑티로 전달하는 클래스입니다.
실제 개발에서의 어댑터 패턴 구현 방법
1) 자바 라이브러리 또는 프레임워크에서의 활용
어댑터 패턴은 자바의 I/O 라이브러리에서 자주 볼 수 있습니다. 예를 들어, InputStreamReader는 바이트 스트림을 문자 스트림으로 변환하는 어댑터 역할을 하는데요. 이 클래스는 InputStream을 받아서 Reader를 제공함으로써 바이트를 문자로 변환하는 과정을 캡슐화하고 있습니다.
<출처: docs.oracle.com>
이 밖에도 어댑터 패턴의 활용은 스프링 프레임워크(Spring Framework)에서도 자주 볼 수 있는데요. 스프링 프레임워크의 MVC 아키텍처에서 HandlerAdapter 인터페이스는 어댑터 패턴의 일반적인 예라고 할 수 있습니다. 참고로 HandlerAdapter 인터페이스는 다양한 종류의 핸들러를 동일한 방식으로 처리하는데 활용됩니다.
<출처: docs.spring.io>
2) 어댑터 패턴 구현 방법
실제 개발 과정에서 어댑터 패턴을 구현할 때는 ① 패턴을 적용하고자 하는 인터페이스 식별하기 ② 어댑터 클래스 작성 ③ 클라이언트 코드에서 호출하는 과정을 거치게 됩니다. 예를 들어, 아래와 같이 호환되지 않는 인터페이스와 클래스가 있는 경우를 살펴보겠습니다.
<출처: 작가>
위 두 인터페이스와 클래스를 호환시키기 위해서는 아래 그림처럼 어댑터 클래스를 작성하여 어댑터 패턴을 적용해 볼 수 있습니다. 아래 예시 코드의 어댑터 클래스는 타겟 인터페이스를 구현했으며, 어댑티 클래스를 멤버 변수로 가지고 있습니다.
<출처: 작가>
이제 클라이언트에서 아래 그림과 같이 코드를 작성하여 Adaptee 클래스의 performAction() 메소드를 호출할 수 있습니다. 즉, 클라이언트에서 Target 인터페이스의 doSomething() 메소드를 호출하여, Adaptee 클래스의 performAction() 메소드를 호출할 수 있게 된 것입니다. 이를 통해 클라이언트는 직접 Adaptee 클래스를 호출하지 않고도 자신이 원하는 인터페이스를 통해, Adaptee 클래스의 기능을 사용할 수 있는 것입니다.
<출처: 작가>
어댑터 패턴을 다시 한번 사용해보자
1. interface 두개가 있다. 이는 110v 와 220v 스펙을 쓸 수 있다는 것이다.
public interface IElectronic110v {
void connect();
}
public interface IElectronic220v {
void connect();
}
2. 각각 에어컨은 220v , 헤어드라이기는 110v 스펙을 가지고 있다.
// 에어컨
public class AirConditioner implements IElectronic220v{
@Override
public void connect() {
System.out.println("에어컨 연결 220v ON");
}
}
// 일본에서 선물받은 헤어 드라이기
public class HairDryer implements IElectronic110v{
@Override
public void connect() {
System.out.println("헤어 드라이기 연결 110v ON");
}
}
3. 그런데 집의 플러그는 220v만 사용이 가능하다. 따라서 dryer는 스펙이 다르게 된다.
public class MyHouse {
// 전기 연결
public static void homeConnect(IElectronic220v iElectronic220v) {
iElectronic220v.connect();
}
public static void main(String[] args) {
HairDryer dryer = new HairDryer();
AirConditioner airConditioner = new AirConditioner();
// 전기 연결 동작
homeConnect(airConditioner);
// homeConnect(dryer); // --> 연결 불가 스펙이 다름 ---> 어댑터 클래스 필요
}
}
4. 이때 필요한 것이 어댑터이다.
// 어댑터 클래스 만들기
// 먼저 기존에 사용하는 스펙을 구현해 준다.
public class ElectornicAdapter implements IElectronic220v{
// 2. 변환 하고자 하는 스펙을 포함 관계로 만들어 준다.
private IElectronic110v iElectronic110v;
// 3. 생성자 주입을 통해서 의존 주입을 받을 수 있도록 설계한다.
public ElectornicAdapter(IElectronic110v iElectronic110v) {
this.iElectronic110v = iElectronic110v;
}
@Override
public void connect() {
// 호환 처리
iElectronic110v.connect();
}
}
5. 이제 작동을 시켜보자.
public class MyHouse {
// 전기 연결
public static void homeConnect(IElectronic220v iElectronic220v) {
iElectronic220v.connect();
}
public static void main(String[] args) {
HairDryer dryer = new HairDryer();
AirConditioner airConditioner = new AirConditioner();
// 전기 연결 동작
homeConnect(airConditioner);
// 동작이 가능해졌다
ElectornicAdapter hairDryerAdapter = new ElectornicAdapter(dryer);
homeConnect(hairDryerAdapter);
}
}
어댑터 패턴의 장단점은?
1) 장점
앞선 예제에서 살펴봤듯이 어댑터 패턴을 이용하면 기존의 클래스를 수정하지 않고도 클라이언트에서 새로운 인터페이스를 사용할 수 있습니다. 이는 기존의 코드를 재사용하고 코드 중복을 줄여주는 데 도움이 되죠. 또한 클래스 간의 결합도를 줄여주어, 소스 코드 변경이 필요할 때 쉽게 수정할 수 있다는 장점도 있습니다.
2) 단점
단점으로는 어댑터 패턴을 사용하면 어댑터 클래스를 추가로 작성해야 하기 때문에 소스 코드가 늘어나게 됩니다. 이는 코드의 복잡성을 증가시키고, 유지 보수를 어렵게 만들 수도 있습니다. 또한 어댑터가 중간에 데이터를 변환하는 과정에서 추가적인 처리 시간과 오버 헤드가 발생할 수도 있습니다.
2) 어댑터 패턴이 필요한 경우
어댑터 패턴은 추가 코드를 작성해야 하고, 오버헤드가 발생할 수도 있기 때문에 무분별한 사용은 권장하지 않습니다. 따라서 호환되지 않는 인터페이스를 가진 클래스들이 함께 작동해야 하거나, 이미 존재하는 클래스의 인터페이스가 요구 사항과 맞지 않거나 또는 기존 클래스에 원하는 인터페이스가 없는 경우 어댑터 패턴을 고려하는 것이 좋습니다.
예를 들어, 서드파티 라이브러리나 API를 사용하는데 그 인터페이스가 애플리케이션 코드와 잘 맞지 않는 경우인데요. 이때 어댑터 패턴을 사용해 서드파티 라이브러리 및 API 내부 구현에 영향을 받지 않으면서, 프로젝트에 필요한 인터페이스를 생성할 수 있습니다.
마치며
지금까지 디자인 패턴 중 구조 패턴의 한 종류인 어댑터 패턴에 대해서 살펴봤습니다. 어댑터 패턴은 인터페이스 사이에 유연성이 필요한 상황에서 효율적으로 사용될 수 있는 디자인 패턴입니다. 다만 앞서 언급했듯이 코드 복잡성을 증가시키고, 오버 헤드를 발생시킬 수 있다는 단점도 있기 때문에 적절한 상황에서만 사용되어야 합니다.
이러한 어댑터 패턴 외에도 인터페이스와 클래스, 객체 간의 관계를 관리하는 디자인 패턴에는 퍼사드 패턴(Facade Pattern), 데코레이터 패턴(Decorator Pattern), 프록시 패턴(Proxy Pattern) 등이 있는데요. 다음 글을 통해 살펴보도록 하겠습니다.
'JAVA' 카테고리의 다른 글
Java - 객체지향 설계 원칙 SOLID (0) | 2024.02.16 |
---|---|
디자인 패턴 - 빌더 패턴 (Builder Pattern) (0) | 2024.02.07 |
디자인 패턴 - 싱글톤 패턴 (Singleton Pattern) (0) | 2024.02.07 |
Java - 디자인 패턴 개념의 이해 (0) | 2024.02.07 |
Java - Timestamp <-> String , 날짜 포맷 (0) | 2024.02.01 |