어댑터 패턴
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.
1. 개요
어댑터 패턴은 호환되지 않는 두 인터페이스가 함께 작동하도록 하는 디자인 패턴이다. 이 패턴은 클라이언트가 예상하는 인터페이스로 기존 클래스의 인터페이스를 변환하여 호환되지 않는 클래스 간의 상호 작용을 가능하게 한다. 어댑터 패턴은 객체 어댑터와 클래스 어댑터의 두 가지 주요 형태로 구현될 수 있으며, 객체 어댑터는 래핑된 객체의 인스턴스를 포함하고 위임을 사용하며, 클래스 어댑터는 다중 상속을 통해 예상 인터페이스와 기존 인터페이스를 모두 구현한다. 어댑터 패턴은 래퍼가 특정 인터페이스를 준수하고 다형적 동작을 지원해야 할 때 유용하며, 데코레이터 패턴, 위임 패턴, 퍼사드 패턴 등 다른 디자인 패턴과 함께 사용될 수 있다.
더 읽어볼만한 페이지
- 소프트웨어 디자인 패턴 - 모델-뷰-컨트롤러
모델-뷰-컨트롤러(MVC)는 소프트웨어 디자인 패턴으로, 응용 프로그램을 모델, 뷰, 컨트롤러 세 가지 요소로 분리하여 개발하며, 사용자 인터페이스 개발에서 데이터, 표현 방식, 사용자 입력 처리를 분리해 유지보수성과 확장성을 높이는 데 기여한다. - 소프트웨어 디자인 패턴 - 스케줄링 (컴퓨팅)
스케줄링은 운영 체제가 시스템의 목적과 환경에 맞춰 작업을 관리하는 기법으로, 장기, 중기, 단기 스케줄러를 통해 프로세스를 선택하며, CPU 사용률, 처리량 등을 기준으로 평가하고, FCFS, SJF, RR 등의 알고리즘을 사용한다.
어댑터 패턴 |
---|
2. 정의
어댑터 디자인 패턴은 유연하고 재사용 가능한 객체 지향 소프트웨어를 설계하기 위해 반복적인 설계 문제를 해결하는 방법을 설명하는 널리 알려진 스물세 가지의 GoF 디자인 패턴 중 하나이다. 즉, 구현, 변경, 테스트 및 재사용이 더 쉬운 객체를 말한다.
어댑터 디자인 패턴은 다음과 같은 문제를 해결한다.
- 클라이언트가 필요로 하는 인터페이스가 없는 클래스를 어떻게 재사용할 수 있는가?
- 호환되지 않는 인터페이스를 가진 클래스들이 어떻게 함께 작동할 수 있는가?
- 클래스에 대한 다른 인터페이스는 어떻게 제공할 수 있는가?
종종 (이미 존재하는) 클래스는 인터페이스가 클라이언트가 요구하는 인터페이스를 따르지 않기 때문에 재사용할 수 없다.
어댑터 디자인 패턴은 이러한 문제를 해결하는 방법을 설명한다.
- 클래스 (
어댑티
)의 (호환되지 않는) 인터페이스를 클라이언트가 요구하는 다른 인터페이스 (타겟
)로 변환하는 별도의어댑터
클래스를 정의한다. - 필요한 인터페이스가 없는 클래스를 사용하기 위해 (재사용하기 위해)
어댑터
를 거칩니다.
이 패턴의 핵심 아이디어는 (이미 존재하는) 클래스의 인터페이스를 변경하지 않고, 별도의
어댑터
를 통해 인터페이스를 조정하는 것이다.클라이언트는
타겟
클래스와 직접 작업하는지, 또는 타겟
인터페이스가 없는 클래스를 사용하는 어댑터
를 통해 작업하는지 알지 못한다.어댑터는 호환되지 않는 두 인터페이스가 함께 작동하도록 한다. 이것이 어댑터의 실제 정의이다. 인터페이스는 호환되지 않을 수 있지만 내부 기능은 요구 사항에 적합해야 한다. 어댑터 디자인 패턴은 그렇지 않으면 호환되지 않는 클래스가 한 클래스의 인터페이스를 클라이언트가 예상하는 인터페이스로 변환하여 함께 작동하도록 한다. 상속을 이용한 어댑터는 사용하려는 클래스의 하위 클래스를 생성하고, 해당 하위 클래스에 필요한 인터페이스를 구현하여 실현된다. 위임을 이용한 어댑터는 사용하고 싶은 클래스의 인스턴스를 생성하고, 그 인스턴스를 다른 클래스에서 사용함으로써 실현된다.
3. 구조
어댑터 디자인 패턴은 GoF 디자인 패턴 중 하나로, 유연하고 재사용 가능한 객체 지향 소프트웨어 설계를 돕는다. 이 패턴은 호환되지 않는 인터페이스를 가진 클래스들을 함께 작동시키거나, 클라이언트가 요구하는 인터페이스가 없는 클래스를 재사용할 수 있게 해준다.
어댑터 패턴은 별도의 `어댑터` 클래스를 통해 기존 클래스의 인터페이스를 변경하지 않고 인터페이스를 조정한다. 클라이언트는 `타겟` 클래스와 직접 작업하는지, `어댑터`를 통해 작업하는지 알지 못한다.
어댑터 패턴에는 두 가지 주요 방식이 있다.
- 객체 어댑터 (Object Adapter): 런타임에 `adaptee` 객체에 위임하여 `target` 인터페이스를 구현한다.
- 클래스 어댑터 (Class Adapter): 컴파일 타임에 `adaptee` 클래스를 상속받아 `target` 인터페이스를 구현한다.
3. 1. 객체 어댑터 (Object Adapter)
이 어댑터 패턴에서 어댑터는 래핑하는 클래스의 인스턴스를 포함한다. 이러한 상황에서 어댑터는 래핑된 객체의 인스턴스에 대한 호출을 수행한다.

위임을 이용한 어댑터는 사용하고 싶은 클래스의 인스턴스를 생성하고, 그 인스턴스를 다른 클래스에서 사용함으로써 실현된다.
3. 2. 클래스 어댑터 (Class Adapter)
이 어댑터 패턴은 예상되는 인터페이스와 기존 인터페이스를 모두 구현하거나 상속하는 여러 개의 다형성 인터페이스를 사용한다. 예상되는 인터페이스는 특히 Java와 같이 클래스의 다중 상속을 지원하지 않는 프로그래밍 언어 (JDK 1.8 이전)에서 순수한 인터페이스 클래스로 생성되는 것이 일반적이다.

상속을 이용한 어댑터는 사용하려는 클래스의 하위 클래스를 생성하고, 해당 하위 클래스에 필요한 인터페이스를 구현하여 실현된다.
3. 3. UML 클래스 다이어그램

위의 UML 클래스 다이어그램에서 `client` 클래스는 `target` 인터페이스가 필요하지만, `adaptee` 클래스의 인터페이스가 `target` 인터페이스를 따르지 않기 때문에 `adaptee` 클래스를 직접 재사용할 수 없다.
대신 `client`는 `adaptee`를 기반으로 `target` 인터페이스를 구현하는 `adapter` 클래스를 통해 작동한다.
- `object adapter` 방식은 런타임에 `adaptee` 객체에 위임하여 `target` 인터페이스를 구현한다 (`adaptee.specificOperation()`).
- `class adapter` 방식은 컴파일 타임에 `adaptee` 클래스를 상속받아 `target` 인터페이스를 구현한다 (`specificOperation()`).
상속을 이용한 어댑터의 클래스 다이어그램은 다음과 같다.
참고로 위 샘플 코드와 이 클래스 다이어그램의 대응 관계는 다음과 같다.
항목 | 내용 |
---|---|
Target | ProductPrice |
Target#requiredMethod | ProductPrice#getPrice() |
Adapter | ProductAdapter |
Adapter#requiredMethod | ProductAdapter#getPrice() |
Adaptee | Product |
Adaptee#oldMethod | Product#getCost() |
위임을 이용한 어댑터의 클래스 다이어그램은 다음과 같다.
※ 위 그림에서 extends는 implements로도 가능하다.
샘플 코드의 대응 관계는 다음과 같다.
항목 | 내용 |
---|---|
Target | ProductPrice |
Target#requiredMethod() | ProductPrice#getPrice() |
Adapter | ProductAdapter |
Adapter#requiredMethod() | ProductAdapter#getPrice() |
Adaptee | Product |
Adaptee#oldMethod() | Product#getCost() |
4. 예제
java
interface ProductPrice {
int getPrice();
}
class Product {
private int cost;
int getCost() {
return cost;
}
}
class ProductAdapter implements ProductPrice {
private Product product = new Product();
public int getPrice() {
return product.getCost();
}
}
```
`Product` 클래스는 기존 클래스이며 수정할 수 없는 것으로 가정한다. `Product` 클래스를 사용하려는 개발자가 `getPrice`라는 메서드로 `Product`의 가격을 얻고 싶어하는 경우, `ProductAdapter`라는 어댑터를 생성함으로써 기존 클래스(`Product`)를 수정하지 않고도 다른 인터페이스를 가질 수 있다. 이처럼 기존 클래스를 수정하지 않고 다른 인터페이스를 가지게 하는 것이 어댑터 패턴의 역할이다.
```java
interface ILightningPhone {
void recharge();
void useLightning();
}
interface IMicroUsbPhone {
void recharge();
void useMicroUsb();
}
class Iphone implements ILightningPhone {
private boolean connector;
@Override
public void useLightning() {
connector = true;
System.out.println("Lightning connected");
}
@Override
public void recharge() {
if (connector) {
System.out.println("Recharge started");
System.out.println("Recharge finished");
} else {
System.out.println("Connect Lightning first");
}
}
}
class Android implements IMicroUsbPhone {
private boolean connector;
@Override
public void useMicroUsb() {
connector = true;
System.out.println("MicroUsb connected");
}
@Override
public void recharge() {
if (connector) {
System.out.println("Recharge started");
System.out.println("Recharge finished");
} else {
System.out.println("Connect MicroUsb first");
}
}
}
/* exposing the target interface while wrapping source object */
class LightningToMicroUsbAdapter implements IMicroUsbPhone {
private final ILightningPhone lightningPhone;
public LightningToMicroUsbAdapter(ILightningPhone lightningPhone) {
this.lightningPhone = lightningPhone;
}
@Override
public void useMicroUsb() {
System.out.println("MicroUsb connected");
lightningPhone.useLightning();
}
@Override
public void recharge() {
lightningPhone.recharge();
}
}
public class AdapterDemo {
static void rechargeMicroUsbPhone(IMicroUsbPhone phone) {
phone.useMicroUsb();
phone.recharge();
}
static void rechargeLightningPhone(ILightningPhone phone) {
phone.useLightning();
phone.recharge();
}
public static void main(String[] args) {
Android android = new Android();
Iphone iPhone = new Iphone();
System.out.println("Recharging android with MicroUsb");
rechargeMicroUsbPhone(android);
System.out.println("Recharging iPhone with Lightning");
rechargeLightningPhone(iPhone);
System.out.println("Recharging iPhone with MicroUsb");
rechargeMicroUsbPhone(new LightningToMicroUsbAdapter(iPhone));
}
}
```
```text
Recharging android with MicroUsb
MicroUsb connected
Recharge started
Recharge finished
Recharging iPhone with Lightning
Lightning connected
Recharge started
Recharge finished
Recharging iPhone with MicroUsb
MicroUsb connected
Lightning connected
Recharge started
Recharge finished
```
위 코드는 어댑터 패턴의 실제 작동 방식을 보여주는 Java 예제이다.
- 인터페이스:
- `ILightningPhone`: 라이트닝 케이블을 사용하는 기기 (예: 아이폰)의 인터페이스. `recharge()` (충전)와 `useLightning()` (라이트닝 케이블 사용) 메서드를 정의한다.
- `IMicroUsbPhone`: Micro USB 케이블을 사용하는 기기 (예: 안드로이드 폰)의 인터페이스. `recharge()`와 `useMicroUsb()` 메서드를 정의한다.
- 클래스:
- `Iphone`: `ILightningPhone` 인터페이스를 구현하는 아이폰 클래스.
- `Android`: `IMicroUsbPhone` 인터페이스를 구현하는 안드로이드 폰 클래스.
- `LightningToMicroUsbAdapter`: `IMicroUsbPhone` 인터페이스를 구현하는 어댑터 클래스. 이 클래스는 `ILightningPhone` 객체(아이폰)를 감싸서, Micro USB 인터페이스를 통해 충전할 수 있게 해준다.
- `AdapterDemo` 클래스:
- `rechargeMicroUsbPhone()`: `IMicroUsbPhone` 인터페이스를 사용하는 기기를 충전하는 메서드.
- `rechargeLightningPhone()`: `ILightningPhone` 인터페이스를 사용하는 기기를 충전하는 메서드.
- `main()`: 어댑터 패턴의 사용 예시를 보여주는 메인 메서드. 안드로이드 폰, 아이폰, 그리고 어댑터를 사용하여 아이폰을 Micro USB로 충전하는 시나리오를 시연한다.
실행 결과:```text
Recharging android with MicroUsb
MicroUsb connected
Recharge started
Recharge finished
Recharging iPhone with Lightning
Lightning connected
Recharge started
Recharge finished
Recharging iPhone with MicroUsb
MicroUsb connected
Lightning connected
Recharge started
Recharge finished
```
위 출력 결과는 어댑터(`LightningToMicroUsbAdapter`)를 통해 아이폰(`Iphone`)이 MicroUSB 인터페이스를 사용하여 충전되는 것을 보여준다. 즉, 서로 다른 인터페이스를 가진 객체들이 어댑터를 통해 함께 작동하는 것을 확인할 수 있다.
4. 1. 오브젝트 어댑터 (Python)
python# Python 코드 샘플
class Target(object):
def specific_request(self):
return 'Hello Adapter Pattern!'
class Adapter(object):
def __init__(self, adaptee):
self.adaptee = adaptee
def request(self):
return self.adaptee.specific_request()
client = Adapter(Target())
print(client.request())
```
```python
"""
어댑터 패턴 예시.
"""
from abc import ABCMeta, abstractmethod
NOT_IMPLEMENTED = "구현해야 합니다."
RECHARGE = ["충전 시작.", "충전 완료."]
POWER_ADAPTERS = {"Android": "MicroUSB", "iPhone": "Lightning"}
CONNECTED = "{} 연결됨."
CONNECT_FIRST = "먼저 {}를 연결하세요."
class RechargeTemplate(metaclass=ABCMeta):
@abstractmethod
def recharge(self):
raise NotImplementedError(NOT_IMPLEMENTED)
class FormatIPhone(RechargeTemplate):
@abstractmethod
def use_lightning(self):
raise NotImplementedError(NOT_IMPLEMENTED)
class FormatAndroid(RechargeTemplate):
@abstractmethod
def use_micro_usb(self):
raise NotImplementedError(NOT_IMPLEMENTED)
class IPhone(FormatIPhone):
__name__ = "iPhone"
def __init__(self):
self.connector = False
def use_lightning(self):
self.connector = True
print(CONNECTED.format(POWER_ADAPTERS[self.__name__]))
def recharge(self):
if self.connector:
for state in RECHARGE:
print(state)
else:
print(CONNECT_FIRST.format(POWER_ADAPTERS[self.__name__]))
class Android(FormatAndroid):
__name__ = "Android"
def __init__(self):
self.connector = False
def use_micro_usb(self):
self.connector = True
print(CONNECTED.format(POWER_ADAPTERS[self.__name__]))
def recharge(self):
if self.connector:
for state in RECHARGE:
print(state)
else:
print(CONNECT_FIRST.format(POWER_ADAPTERS[self.__name__]))
class IPhoneAdapter(FormatAndroid):
def __init__(self, mobile):
self.mobile = mobile
def recharge(self):
self.mobile.recharge()
def use_micro_usb(self):
print(CONNECTED.format(POWER_ADAPTERS["Android"]))
self.mobile.use_lightning()
class AndroidRecharger:
def __init__(self):
self.phone = Android()
self.phone.use_micro_usb()
self.phone.recharge()
class IPhoneMicroUSBRecharger:
def __init__(self):
self.phone = IPhone()
self.phone_adapter = IPhoneAdapter(self.phone)
self.phone_adapter.use_micro_usb()
self.phone_adapter.recharge()
class IPhoneRecharger:
def __init__(self):
self.phone = IPhone()
self.phone.use_lightning()
self.phone.recharge()
print("MicroUSB 충전기를 사용하여 Android 충전.")
AndroidRecharger()
print()
print("어댑터 패턴을 사용하여 MicroUSB로 iPhone 충전.")
IPhoneMicroUSBRecharger()
print()
print("iPhone 충전기를 사용하여 iPhone 충전.")
IPhoneRecharger()
4. 2. 클래스 어댑터 (Java)
javainterface ProductPrice{
public int getPrice();
}
class Product{
private int cost;
public int getCost(){
return cost;
}
}
class ProductAdapter extends Product implements ProductPrice{
public int getPrice(){
return this.getCost();
}
}
```
`Product`클래스는 기존 클래스이며 수정할 수 없는 것으로 가정한다.
여기서 `Product` 클래스를 사용하려는 개발자가 있고, 해당 개발자는 `getPrice`라는 메서드로 `Product`의 가격을 얻고 싶어 한다고 하자.
이 경우, `ProductAdapter`라는 어댑터를 생성함으로써, 기존 클래스(`Product`)를 수정하지 않고도 다른 인터페이스를 가질 수 있다.
이처럼 기존 클래스를 수정하지 않고 다른 인터페이스를 가지게 하는 것이 '''어댑터 패턴'''의 역할이다.
4. 3. 런타임 어댑터 (Java)
ClassA영어가 ClassB영어에 데이터를 제공하려고 하며, 예를 들어 어떤 String영어 데이터를 제공한다고 가정해 본다. 컴파일 타임 해결 방법은 다음과 같다.```java
classB.setStringData(classA.getStringData());
```
그러나 문자열 데이터의 형식을 변경해야 한다고 가정해 보자. 컴파일 타임 해결 방법은 상속을 사용하는 것이다.
```java
public class Format1ClassA extends ClassA {
@Override
public String getStringData() {
return format(toString());
}
}
```
그리고 아마도 팩토리 패턴을 사용하여 런타임에 올바르게 "형식화"된 객체를 생성할 수 있다. "어댑터"를 사용하는 솔루션은 다음과 같이 진행된다.
# 중간 "제공자(provider)" 인터페이스를 정의하고, 데이터 소스(이 예에서는 ClassA영어)를 래핑하고 적절하게 형식이 지정된 데이터를 출력하는 해당 제공자 인터페이스의 구현을 작성한다.
public interface StringProvider {
public String getStringData();
}
public class ClassAFormat1 implements StringProvider {
private ClassA classA = null;
public ClassAFormat1(final ClassA a) {
classA = a;
}
public String getStringData() {
return format(classA.getStringData());
}
private String format(final String sourceValue) {
// Manipulate the source string into a format required
// by the object needing the source object's data
return sourceValue.trim();
}
}
# 제공자의 특정 구현을 반환하는 어댑터 클래스를 작성한다.
public class ClassAFormat1Adapter extends Adapter {
public Object adapt(final Object anObject) {
return new ClassAFormat1((ClassA) anObject);
}
}
# 어댑터영어를 전역 레지스트리에 등록하여 런타임에 어댑터영어를 조회할 수 있도록 한다.
AdapterFactory.getInstance().registerAdapter(ClassA.class, ClassAFormat1Adapter.class, "format1");
# 코드에서 ClassA영어에서 ClassB영어로 데이터를 전송하려면 다음과 같이 작성한다.
Adapter adapter =
AdapterFactory.getInstance()
.getAdapterFromTo(ClassA.class, StringProvider.class, "format1");
StringProvider provider = (StringProvider) adapter.adapt(classA);
String string = provider.getStringData();
classB.setStringData(string);
또는 더 간결하게:
classB.setStringData(
((StringProvider)
AdapterFactory.getInstance()
.getAdapterFromTo(ClassA.class, StringProvider.class, "format1")
.adapt(classA))
.getStringData());
# 두 번째 형식으로 데이터를 전송하려는 경우 다른 어댑터/제공자를 조회하면 이점을 확인할 수 있다.
Adapter adapter =
AdapterFactory.getInstance()
.getAdapterFromTo(ClassA.class, StringProvider.class, "format2");
# 그리고 ClassA영어에서 데이터를 예를 들어 Class C영어의 이미지 데이터로 출력하려는 경우:
Adapter adapter =
AdapterFactory.getInstance()
.getAdapterFromTo(ClassA.class, ImageProvider.class, "format2");
ImageProvider provider = (ImageProvider) adapter.adapt(classA);
classC.setImage(provider.getImage());
이러한 방식으로 어댑터와 제공자를 사용하면 ClassB영어와 ClassC영어가 클래스 계층 구조를 변경하지 않고 ClassA영어를 여러 "뷰"로 볼 수 있다. 일반적으로 기존 객체 계층 구조에 개조할 수 있는 객체 간의 임의 데이터 흐름을 위한 메커니즘을 허용한다.
4. 4. 실생활 예제 (Java)
javainterface ILightningPhone {
void recharge();
void useLightning();
}
interface IMicroUsbPhone {
void recharge();
void useMicroUsb();
}
class Iphone implements ILightningPhone {
private boolean connector;
@Override
public void useLightning() {
this.connector = true;
System.out.println("Lightning connected");
}
@Override
public void recharge() {
if (this.connector) {
System.out.println("Recharge started");
System.out.println("Recharge finished");
} else {
System.out.println("Connect Lightning first");
}
}
}
class Android implements IMicroUsbPhone {
private boolean connector;
@Override
public void useMicroUsb() {
this.connector = true;
System.out.println("MicroUsb connected");
}
@Override
public void recharge() {
if (this.connector) {
System.out.println("Recharge started");
System.out.println("Recharge finished");
} else {
System.out.println("Connect MicroUsb first");
}
}
}
/* exposing the target interface while wrapping source object */
class LightningToMicroUsbAdapter implements IMicroUsbPhone {
private final ILightningPhone lightningPhone;
public LightningToMicroUsbAdapter(ILightningPhone lightningPhone) {
this.lightningPhone = lightningPhone;
}
@Override
public void useMicroUsb() {
System.out.println("MicroUsb connected");
this.lightningPhone.useLightning();
}
@Override
public void recharge() {
this.lightningPhone.recharge();
}
}
public class AdapterDemo {
static void rechargeMicroUsbPhone(IMicroUsbPhone phone) {
phone.useMicroUsb();
phone.recharge();
}
static void rechargeLightningPhone(ILightningPhone phone) {
phone.useLightning();
phone.recharge();
}
public static void main(String[] args) {
Android android = new Android();
Iphone iPhone = new Iphone();
System.out.println("Recharging android with MicroUsb");
rechargeMicroUsbPhone(android);
System.out.println("Recharging iPhone with Lightning");
rechargeLightningPhone(iPhone);
System.out.println("Recharging iPhone with MicroUsb");
rechargeMicroUsbPhone(new LightningToMicroUsbAdapter(iPhone));
}
}
```
```text
Recharging android with MicroUsb
MicroUsb connected
Recharge started
Recharge finished
Recharging iPhone with Lightning
Lightning connected
Recharge started
Recharge finished
Recharging iPhone with MicroUsb
MicroUsb connected
Lightning connected
Recharge started
Recharge finished
```
위 코드는 어댑터 패턴의 실제 작동 방식을 보여주는 Java 예제이다.
- 인터페이스:
- `ILightningPhone`: 라이트닝 케이블을 사용하는 기기 (예: 아이폰)의 인터페이스. `recharge()` (충전)와 `useLightning()` (라이트닝 케이블 사용) 메서드를 정의한다.
- `IMicroUsbPhone`: Micro USB 케이블을 사용하는 기기 (예: 안드로이드 폰)의 인터페이스. `recharge()`와 `useMicroUsb()` 메서드를 정의한다.
- 클래스:
- `Iphone`: `ILightningPhone` 인터페이스를 구현하는 아이폰 클래스.
- `Android`: `IMicroUsbPhone` 인터페이스를 구현하는 안드로이드 폰 클래스.
- `LightningToMicroUsbAdapter`: `IMicroUsbPhone` 인터페이스를 구현하는 어댑터 클래스. 이 클래스는 `ILightningPhone` 객체(아이폰)를 감싸서, Micro USB 인터페이스를 통해 충전할 수 있게 해준다.
- `AdapterDemo` 클래스:
- `rechargeMicroUsbPhone()`: `IMicroUsbPhone` 인터페이스를 사용하는 기기를 충전하는 메서드.
- `rechargeLightningPhone()`: `ILightningPhone` 인터페이스를 사용하는 기기를 충전하는 메서드.
- `main()`: 어댑터 패턴의 사용 예시를 보여주는 메인 메서드. 안드로이드 폰, 아이폰, 그리고 어댑터를 사용하여 아이폰을 Micro USB로 충전하는 시나리오를 시연한다.
실행 결과:```text
Recharging android with MicroUsb
MicroUsb connected
Recharge started
Recharge finished
Recharging iPhone with Lightning
Lightning connected
Recharge started
Recharge finished
Recharging iPhone with MicroUsb
MicroUsb connected
Lightning connected
Recharge started
Recharge finished
```
위 출력 결과는 어댑터(`LightningToMicroUsbAdapter`)를 통해 아이폰(`Iphone`)이 MicroUSB 인터페이스를 사용하여 충전되는 것을 보여준다. 즉, 서로 다른 인터페이스를 가진 객체들이 어댑터를 통해 함께 작동하는 것을 확인할 수 있다.
5. 장점 및 단점
어댑터 패턴은 특정 인터페이스를 다른 인터페이스에 맞게 변환하여 호환성을 제공하는 유용한 패턴이지만, 몇 가지 고려해야 할 장점과 단점이 있다.
장점
- 재사용성 향상: 기존 코드를 수정하지 않고도 다른 인터페이스를 가진 클래스와 함께 사용할 수 있도록 하여 코드 재사용성을 높인다.
- 유연성 증가: 어댑터 패턴을 사용하면 클라이언트 코드를 변경하지 않고도 다양한 어댑티(Adaptee)를 사용할 수 있다.
- 호환성 문제 해결: 서로 다른 인터페이스를 가진 클래스 간의 호환성 문제를 해결하여 시스템 통합을 용이하게 한다.
- 개방-폐쇄 원칙(OCP) 준수: 기존 코드를 변경하지 않고도 새로운 어댑터를 추가하여 기능을 확장할 수 있다.
단점
- 복잡성 증가: 어댑터 클래스를 추가해야 하므로 시스템의 복잡성이 다소 증가할 수 있다.
- 런타임 오버헤드: 어댑터를 통해 메서드 호출이 이루어지므로 약간의 런타임 오버헤드가 발생할 수 있다.
- 가독성 저하: 여러 어댑터가 중첩되어 사용될 경우 코드의 가독성이 저하될 수 있다.
하지만 이러한 단점에도 불구하고 어댑터 패턴은 시스템의 유연성과 재사용성을 높이는 데 매우 유용한 패턴이다.
6. 다른 패턴과의 관계
어댑터 디자인 패턴은 데코레이터, 위임, 퍼사드 등 다른 패턴들과 관계를 가진다.
어댑터는 래퍼가 특정 인터페이스를 준수해야 하고 다형적 동작을 지원해야 할 때 사용할 수 있다. 반면, 데코레이터는 런타임에 인터페이스의 동작을 추가하거나 변경할 수 있게 해주며, 퍼사드는 기본 객체에 대한 더 쉽거나 간단한 인터페이스가 필요할 때 사용된다.
패턴 | 의도 |
---|---|
어댑터 또는 래퍼 | 클라이언트가 기대하는 것과 일치하도록 한 인터페이스를 다른 인터페이스로 변환 |
데코레이터 | 원래 코드를 래핑하여 인터페이스에 책임을 동적으로 추가 |
위임 | "상속보다 구성" 지원 |
퍼사드 | 단순화된 인터페이스 제공 |
본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.
문의하기 : help@durumis.com