인터페이스의 연결고리
해외여행을 할 때 가장 먼저 챙겨야 할 물건 중 하나는 전기 어댑터입니다. 한국의 220V 전기 제품을 일본이나 미국의 110V 콘센트에 연결할 수는 없습니다. 이럴 때 필요한 것이 전기 어댑터입니다. 어댑터는 서로 다른 규격 사이의 다리 역할을 하여 호환되지 않는 두 시스템이 함께 동작할 수 있게 합니다.
어댑터 패턴을 처음으로 제안한 사람이 누군지는 정확하지 않지만, 패턴이 공식적으로 문서화된 것은 1994년 GoF의 책을 통해서였습니다. 어댑터라는 개념 자체는 전기 플러그 어댑터와 같은 일상적인 물건에서 영감을 받은 것으로 보이며, Smalltalk와 같은 초기 객체지향 언어에서 이미 사용되고 있었습니다.
소프트웨어에서 어댑터 사용은 서로 다른 코드를 연결할 때 사용합니다. 여기에서 서로 다른 코드란 언어자체가 완전히 다른 코드가 아닌, 같은 언어입니다. 예를 들어 건물을 리모델링할 때를 생각하면 됩니다. 리모델링은 기존 그대로의 형태는 유지하되, 내부 또는 외부를 새롭게 바꾸는 것입니다. 소프트웨어서도 이전의 시스템(레거시 시스템-Lagacy system이라고 합니다.)은 유지하되 새로운 시스템으로 통합하고자 할 때 기존 시스템의 코드와 새로운 시스템의 코드를 연결할 때 어댑터 패턴을 사용할 수 있습니다.
또는 외부 라이브러리를 사용할 때도 어댑터 패턴을 사용하여 자신의 코드로 외부 라이브러리를 사용할 수 있고, 이미 완성된 코드의 인터페이스만 변경할 때도 이 패턴을 사용할 수 있습니다.
어댑터 패턴에는 주로 클래스 어댑터와 객체 어댑터로 구현합니다. 클래스 어댑터는 상속이라는 개념으로 구현하고, 객체 어댑터는 합성이라는 개념으로 구현합니다.
// 타겟 인터페이스
interface Target {
void request();
}
// 레거시 (기존 클래스)
class LegacyCode {
void specificRequest() {
System.out.println("Legacy code's specific request");
}
}
// 클래스 어댑터 - LegacyCode를 상속 받음.
class ClassAdapter extends LegacyCode implements Target {
@Override
public void request() {
// 타겟 인터페이스의 메서드를 어댑티의 메서드로 변환
specificRequest();
}
}
위의 코드는 클래스 어댑터 방식으로 구현한 코드입니다. 레거시 코드의 모든 함수(메서드)를 직접 사용할 수 있고, 레거시 코드에 있는 메서드를 오버라이드(재정의)해서 변경할 수도 있습니다. 다만, JAVA는 다중 상속(extends)을 지원하지 않아 제약이 있습니다. 또한 특정 클래스에서만 동작하며, 하위 클래스에는 적용할 수가 없습니다.
// 타겟 인터페이스
interface Target {
void request();
}
// 레거시 (기존 클래스)
class LegacyCode {
void specificRequest() {
System.out.println("Legacy code's specific request");
}
}
// 객체 어댑터
class ObjectAdapter implements Target {
private LegacyCode legacyCode;
public ObjectAdapter(LegacyCode legacyCode) {
this.legacyCode = legacyCode;
}
@Override
public void request() {
// 타겟 인터페이스의 메서드를 레거시의 메서드로 변환
adaptee.specificRequest();
}
}
객체 어댑터 구현방식입니다. LegacyCode를 객체로 전달받아 처리하기에, 하위클래스를 객체로 받으면 하위 클래스에 있는 메서드까지 작동할 수 있습니다. 코드에는 LegacyCode 객체만 있지만, 다중으로 여러 개의 객체를 받아서 처리가 가능합니다. 다만, 클래스 어댑터보다 구현이 좀 더 복잡할 수 있고, 객체를 생성하므로 메모리 사용량이 증가할 수 있습니다. (객체를 생성할 때 생성된 객체는 메모리에 올라갑니다.)
어댑터는 서로 다른 것을 연결해 줍니다. 전기 어댑터는 220V에서 110V가 가능하게 연결해 주고, 번역도 다른 언어로 되어 있는 글을 한글로 연결해 줍니다. 또한 CType의 코드를 일반 USB Type으로 변환해 주는 것도 어댑터의 역할입니다.
이처럼 어댑터는 중간 연결을 원활하게 해 줍니다. 연결은 소통과 같습니다. 중간의 연결이 잘못되면 이후의 결과가 다르게 나올 수 있습니다. 코드도 어댑터가 잘못 연결되면 전혀 다른 값이 나올 수 있기에, 어댑터의 역할은 중요합니다.
어댑터는 연결하려는 두 곳(또는 여러 곳)의 특성을 알고 있어야 합니다. 소프트웨어로 봤을 때 초보가 하기에는 힘듭니다. 두 시스템을 알고 있어야 어댑터로써 제대로 된 동작이 가능합니다. 번역이나 통역을 생각했을 때도, 두 언어의 특징을 이해하고 알고 있어야 제대로 된 통역이 가능합니다.
연결이 필요한 곳은 언제나 어댑터가 존재합니다. 단순 연결이 아닌 연결하려는 곳을 이해하고 있어야 합니다. ‘선무당’이라는 말이 있습니다. 서투르고 미숙하여 굿을 제대로 하지 못하는 무당을 말합니다. 이 말은 제대로 알지 못하는데, 굉장히 많이 아는 척하는 사람을 일컫는 말이기도 합니다. 프로그래밍에서도 ‘선무당’처럼 제대로 시스템을 이해하지 못하고 코드를 작성하는 경우가 있습니다.
코드를 작성하는 것도 어댑터와 같습니다. 만들려고 하는 시스템을 코드라는 어댑터로 컴퓨터가 실행할 수 있도록 작성하는 작업입니다. 내가 만들려는 시스템을 이해하고 알았을 때, 제대로 된 실행 결과가 나올 수 있습니다.