Chapter 13. 어댑터 패턴(Adapter Pattern)
카테고리: Design Pattern
인프런에 있는 이재환님의 강의 게임 디자인 패턴 with Unity 를 듣고 정리한 필기입니다. 😀
Chapter 13. Adapter Pattern
🔔 개념
이미 제공되어 있는 것과 필요한 것의 차이를 없애주는 디자인 패턴
- 한 클래스의 인터페이스를 클라이언트에서 시용하고자 하는 다른 인터페이스로 변환한다.
- 어댑터를 이용하면 인터페이스 호환성 문제 때문에 같이 쓸 수 없는 클래스들을 연결해서 쓸 수 있다.
Adapter 패턴은 Wrapper 패턴으로 불리기도 한다.
일반 상품을 예쁜 포장지로 싸서 선물용 상품으로 만드는 것처럼, 무엇인가를 한번 포장해서 다른 용도로 사용할 수 있게 교환해주는 것이 wrapper이며 adapter 이다.
Adapter 패턴의 종류
- 클래스에 의한 Adapter 패턴 (상속을 사용한 Adapter)
- 인스턴스에 의한 Adapter 패턴 (위임을 사용한 Adapter)
Adapter 를 사용하는 경우
- 이미 존재하고 있는, 버그 적은 클래스를 부품으로서 재사용
- Adapter 패턴은 기존의 클래스를 개조해서 필요한 클래스를 만든다.
- 이 패턴으로 필요한 메소드를 빠르게 만들 수 있다.
- 만약 버그가 발생해도 기존의 클래스에는 버그가 없으므로 Adapter 역할의 클래스를 중점적으로 조사하면 되고, 프로그램 검사도 상당히 쉬워진다.
- 이미 만들어진 클래스를 새로운 인터페이스에 맞게 개조시킬때는 당연히 Adapter 패턴을 사용해야 한다.
- Adapter 패턴은 기존의 클래스를 전혀 수정하지 않고 목적한 인터페이스에 맞추려는 것이다.
- 또한 Adapter 패턴에서는 기존 클래스의 소스 프로그램이 반드시 필요한 것은 아니다. 기존 클래스의 사양(Interface)만 알면 새로운 클래스를 만들 수 있다.
밑에 예제들을 보면 이해가 될 것이다.
🔔 예제 1
📜Duck
// 오리 인터페이스
public interface Duck
{
void quack(); // 꽥 울기
void fly(); // 날기
}
📜MallardDuck
// 청둥오리
public class MallardDuck : Duck
{
public void quack()
{
Debug.Log("오리 : 울기(꽥꽥)");
}
public void fly()
{
Debug.Log("오리 : 날기");
}
}
📜Turkey
// 칠면조 인터페이스
public interface Turkey
{
void gobble(); // 울기
void fly(); // 날기
}
📜WildTurkey
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// 야생칠면조
public class WildTurkey : Turkey
{
public void gobble()
{
Debug.Log("칠면조 : 울기(고르륵고르륵)");
}
public void fly()
{
Debug.Log("칠면조 : 날기");
}
}
📜TurkeyAdapter ⭐⭐
// Duck 객체가 모자라서 Turkey 객체를 대신 사용해야 하는 상황
// 인터페이스가 다르기 때문에 Turkey객체를 바로 사용할 수는 없다.
public class TurkeyAdapter : Duck
{
Turkey turkey;
public TurkeyAdapter(Turkey turkey)
{
this.turkey = turkey;
}
public void quack()
{
turkey.gobble();
}
public void fly()
{
turkey.fly();
}
}
Turkey 를 Duck 으로 바꿔주는 어댑터
- Turkey 객체로 Duck 의 인터페이스를 사용하기 위하여
- Duck 인터페이스 상속
- Duck 인터페이스 함수들을 오버라이딩 하되, 함수들 안에서
turkey.gobble()
,turkey.fly()
이렇게 Turkey 의 함수들을 호출해주면 된다.
📜Client
public class Client : MonoBehaviour {
void Start () {
MallardDuck duck = new MallardDuck();
WildTurkey turkey = new WildTurkey();
Duck turkeyAdapter = new TurkeyAdapter(turkey);
Debug.Log("오리 사용...");
testDuck(duck);
Debug.Log("오리 부족. 칠면조로 대체...");
testDuck(turkeyAdapter);
}
void testDuck(Duck duck)
{
// 동일한 방법으로 사용
duck.quack();
duck.fly();
}
}
Duck turkeyAdapter = new TurkeyAdapter(turkey);
- 칠면조 어댑터는
Duck
타입 - 즉, 칠면조 객체로 오리의 인터페이스를 사용할 것
- 칠면조 어댑터는
testDuck(turkeyAdapter);
이렇게 사용이 가능
🔔 예제 2
현재 PayX 결제 시스템을 사용하고 있는데, PayY 결제 시스템으로 변경하고 싶은 상황. 현재까지 PayX 를 사용한 수많은 클래스와 데이터들을 변경하고 싶지 않고, 현재 서비스 되고 있는 많은 부분을 변경하는 것은 위험하다. PayX는 안정적인 상태로 쓰고 있었는데 PayY로 갑자기 변경해서 전부 다 변경해버리면 버그나 다른 위험이 발생할 수도 있다.
📜PayX
// PayX 회사로부터 받은 인터페이스
public interface PayX {
string getCreditCardNum();
void setCreditCardNum (string creditCardNum);
}
📜PayY
// PayY 회사로부터 받은 인터페이스
public interface PayY {
string getCustomerCardNum();
void setCustomerCardNum (string customerCardNum);
}
갈아 타고 싶은 PayY 결제시스템의 인터페이스. PayX 와 사용하는 함수가 다르다.
📜PayImpl
수정 전
// 우리 회사에서 PayX 인터페이스 구현
public class PayImpl : PayX {
private string creditCardNum;
public string getCreditCardNum () {
Debug.Log ("PayX (Get)");
return creditCardNum;
}
public void setCreditCardNum (string creditCardNum) {
Debug.Log ("PayX (Set)");
this.creditCardNum = creditCardNum;
}
}
원래 이렇게 PayX 인터페이스를 오버라이딩해여 PayX 를 사용하고 있는 상태였었다.
수정 후. 어댑터 패턴 사용하여 갈아타기 : 다중 상속
// 우리 회사에서 PayY 인터페이스 구현
public class PayImpl : PayY, PayX {
// for PayY
private string customerCardNum;
public string getCustomerCardNum() {
Debug.Log ("PayY (Get)");
return customerCardNum;
}
public void setCustomerCardNum (string customerCardNum) {
Debug.Log ("PayY (Set)");
this.customerCardNum = customerCardNum;
}
// ------------------------------------------------------
// for PayX Method
public string getCreditCardNum()
{
return getCustomerCardNum();
}
public void setCreditCardNum(string creditCardNum)
{
setCustomerCardNum(creditCardNum);
}
}
PayY 인터페이스 오버라이딩시 아예 PayX 와 PayX 를 둘 다 다중상속 해서 둘 다 오버라이딩한다. 그리고 PayX 의 함수 오버라이딩시 내용을 PayY의 함수를 호출하도록 함수의 내용을 바꿔주면 된다.
- PayX의 getCreditCardNum()함수를 호출하면 PayY의 getCustomerCardNum()함수를 호출하도록
- PayX의 setCreditCardNum()함수를 호출하면 PayY의 setCustomerCardNum()함수를 호출하도록
이 📜PayImpl 가 바로 어댑터가 된다.
📜MyPay
public class MyPay : MonoBehaviour {
void Start () {
// for PayX : 원래 이렇게 사용중 ...
PayImpl myPay = new PayImpl();
myPay.setCreditCardNum("12345");
string myCardNum = myPay.getCreditCardNum();
// Debug.Log("PayX : " + myCardNum);
// for PayY : PayX 와 같은 메서드명을 사용
// 그러므로 코드를 바꿀 필요가 없다.
Debug.Log("PayY : " + myCardNum);
}
}
코드는 바꿀 필요가 전혀 없다.(변화로 인해 위험할 일이 없다) 그러나 내부적으로 setCreditCardNum 는 곧 PayY의 setCustomerCardNum 함수를 부르도록 📜PayImpl 스크립트만 수정했을 뿐이다.
🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄
댓글 남기기