no_mkd
본격적으로 시작하기 전에, 몇 가지 문제를 먼저 생각해 봅시다:
- 기존의 새 프로젝트에서 기존 프로젝트의 대량의 레거시 코드를 활용할 수 있다면, 새 프로젝트를 처음부터 완성할 생각입니까, 아니면 기존 프로젝트의 모듈 기능과 인터페이스를 이해할 생각입니까?
- 레거시 코드를 이해한 후, 몇 가지 중요한 기능 모듈의 인터페이스가 다르다는 것을 발견했다면 (여러 기존 프로젝트에서 왔을 수 있기 때문에), 직접 재사용할 수 없다면 레거시 코드 사용을 포기할 생각입니까?
- 포기할 생각이 없다면 (그렇게 해야 합니다. 레거시 코드의 정확성은 실천으로 검증되었습니다), 나머지 n - 1 개 인터페이스를 다시 작성해야 합니까? 아니면 모든 n 개 인터페이스를 다시 작성해야 합니까?
- 그렇게 하지 않는다면, 다른 간단한 방법이 있습니까?
일. 어댑터 패턴이란?
먼저, 어댑터가 무엇인지 알아야 합니다. 네, 노트북 컴퓨터의 전원 어댑터를 들어보셨죠? 이는 220V 교류 전기를 노트북이 필요로 하는 15V 직류 전기로 변환할 수 있습니다. 놀랍습니다. 작은 전원 어댑터가 가정용 전기와 노트북이 필요로 하는 전기 유형의 불일치 문제를 해결했습니다. 무언가 발견하셨나요? 맞습니다. 우리는 가정용 전기를 바꾼 것도 (15V 직류 전기로 변환), 노트북을 바꾼 것도 (220V 교류 전기로 변환) 아니지만, 확실히 이 문제를 해결했습니다
어댑터 패턴——서로 다른 인터페이스 변환을 실현하기 위한 디자인 패턴
이. 예시
두 개의 캡슐화된 기능 모듈이 있지만, 그것들이 필요로 하는 매개변수가 다르다고 가정해 봅시다 (매개변수의 실질은 동일한 종류의 객체이지만)
예를 들어, A 모듈 (텍스트 검사 모듈) 은 다음과 같습니다:
package AdapterPattern;/**
-
@author ayqy
-
텍스트 검사 모듈 (MSOffice Word 의 "맞춤법 및 문법 검사"와 유사) */ public class TextCheckModule { FormatText text;
public TextCheckModule(FormatText text){ this.text = text; }
/*
- 많은 구체적인 Check 작업 생략.. */ }
A 모듈 진입에는 FormatText 유형의 매개변수가 필요합니다. 그 정의는 다음과 같습니다:
package AdapterPattern;/**
-
@author ayqy
-
포맷된 텍스트 정의 */ public interface FormatText { String text = null;
/**
- @return 텍스트의 논리 행 수 */ public abstract int getLineNumber();
/**
- @param index 행 번호
- @return index 행의 내용 */ public abstract String getLine(int index);
/*
- 다른 유용한 메서드 생략 */ }
B 모듈 (텍스트 표시 모듈) 도 있습니다. 이는 다음과 같습니다:
package AdapterPattern;/**
-
@author ayqy
-
텍스트 표시 모듈 */ public class TextPrintModule { DefaultText text;
public TextPrintModule(DefaultText text){ this.text = text; }
/*
- 많은 표시 관련 작업 생략
- */ }
B 모듈 진입에 필요한 DefaultText:
package AdapterPattern;/**
-
@author ayqy
-
기본 텍스트 정의 */ public interface DefaultText { String text = null;
/**
- @return 텍스트의 논리 행 수 */ public abstract int getLineCount();
/**
- @param index 행 번호
- @return index 행의 내용 */ public abstract String getLineContent(int index);
/*
- 다른 유용한 메서드 생략 */ }
새 프로젝트에서는 워드 프로세싱 프로그램 (MSOffice Word 와 같은) 을 구현해야 합니다. 텍스트 검사 기능을 구현하기 위해 A 모듈을 호출해야 하고, 텍스트 표시 기능을 구현하기 위해 B 모듈을 호출해야 합니다. 그러나 문제는 두 모듈의 인터페이스가 일치하지 않아 기존 A 와 B 를 직접 재사용할 수 없다는 것입니다..
이때 우리는 두 가지 선택지만 있는 것 같습니다:
- FormatText(또는 DefaultText) 를 수정하여 DefaultText(또는 FormatText) 를 만족하게 하고, A(또는 B) 의 내부 구현도 수정해야 합니다
- 세 번째 인터페이스 MyText 를 정의하고, A 와 B 를 수정하여 인터페이스 통일을 위해 MyText 를 매개변수로 사용하게 합니다
물론, 우리는 첫 번째 선택지를 더 선호할 수 있습니다. 필요한 수정이 상대적으로 적기 때문입니다. 그러나 그래도 작업량은 여전히 큽니다. A 의 캡슐화를 열고, 내부 구현을 이해하고, 메서드 호출 세부사항을 수정해야 합니다. 실제로는 더 나은 선택이 있습니다——Adapter 를 정의하여 FormatText 에서 DefaultText 로의 변환 (또는 그 반대) 을 담당하게 하는 것입니다:
package AdapterPattern;/**
-
@author ayqy
-
기본 텍스트 어댑터 정의 */ public class DefaultTextAdapter implements DefaultText{ FormatText formatText = null;//소스 객체
/**
- @param text 변환이 필요한 소스 텍스트 객체 */ public DefaultTextAdapter(FormatText formatText){ this.formatText = formatText; }
@Override public int getLineCount() { int lineNumber;
lineNumber = formatText.getLineNumber(); /* * 여기서 추가 변환 처리 추가 * */ return lineNumber;}
@Override public String getLineContent(int index) { String line;
line = formatText.getLine(index); /* * 여기서 추가 변환 처리 추가 * */ return line;} }
우리의 방식은 매우 간단합니다:
- Adapter 를 정의하여 타겟 인터페이스를 구현합니다
- 소스 인터페이스 객체를 가져와서 유지합니다
- 타겟 인터페이스의 각 메서드를 구현합니다 (메서드 본문에서 소스 인터페이스 객체의 메서드를 호출하고 변환을 실현하기 위한 추가 처리를 추가합니다)
어댑터가 완성되었습니다. 어떻게 사용할까요? Test 클래스를 구현하여 테스트해 봅시다:
package AdapterPattern;/**
-
@author ayqy
-
인터페이스 어댑터 테스트 */ public class Test implements FormatText{
public static void main(String[] args) { //소스 인터페이스 객체 생성 FormatText text = new Test(); //텍스트 검사 모듈 객체 생성 TextCheckModule tcm = new TextCheckModule(text); /tcm 호출하여 텍스트 검사 구현/
//어댑터 객체 생성. 소스 인터페이스 객체에서 타겟 인터페이스 객체로의 변환 실행 DefaultTextAdapter textAdapter = new DefaultTextAdapter(text); //Adapter 를 사용하여 텍스트 표시 모듈 객체 생성 TextPrintModule tpm = new TextPrintModule(textAdapter); /*tcm 호출하여 텍스트 표시 구현*/}
/아래의 게으른 부분을 무시하세요../ @Override public int getLineNumber() { // TODO Auto-generated method stub return 0; }
@Override public String getLine(int index) { // TODO Auto-generated method stub return null; }
}
(P.S. 제 게으른 행동을 용서하세요. FormatText 가 왜 하필 인터페이스인가요..)
물론, Test 에는 실행 결과가 없지만, 컴파일을 통과하면 변환에 문제가 없음을 충분히 설명할 수 있습니다..
실제로 우리는 매우 중요한 문제를 무시했습니다. 예제에서 소스 인터페이스와 타겟 인터페이스의 메서드가 대응됩니다. 다시 말하면: 소스 인터페이스에서 정의된 메서드는 타겟 인터페이스에 유사한 메서드가 대응됩니다. 물론, 이러한 상황은 매우 드뭅니다. 보통 메서드가 대응되지 않는 문제가 존재합니다 (소스 인터페이스에 타겟 인터페이스에서 정의되지 않은 메서드가 존재하거나, 또는 그 반대의 상황)
이때 우리는 두 가지 선택이 있습니다:
- 예외를 던지지만, 주석 또는 문서에서 자세한 설명을 해야 합니다. 다음과 같이:
throw new UnsupportedOperationException();//소스 인터페이스는 이 작업을 지원하지 않습니다
- 빈 구현을 완성합니다. 예를 들어, return false, 0, null 등
어떤 것을 선택할지는 구체적인 상황에 따라 다릅니다. 각각 장점이 있으며, 일반화할 수 없습니다
삼. 또 다른 어댑터 구현 방식
예제에서는 "소스 인터페이스 객체를 유지하고 타겟 인터페이스를 구현하는" 방식을 사용하여 어댑터를 구현했습니다. 실제로는 또 다른 방식이 존재합니다——다중 상속 (또는 여러 인터페이스 구현)
Adapter 클래스가 A 인터페이스와 B 인터페이스를 모두 구현했다면, 의심할 여지 없이, Adapter 객체는 A 유형이기도 하고 B 유형이기도 합니다 (다중 상속의 원리는 유사합니다..)
Java 는 다중 상속을 지원하지 않지만, 다중 상속을 지원하는 언어 환경에서는 이러한 구현 방식을 생각해야 합니다. 그런 후 구체적인 상황에 따라 다중 상속을 사용하여 Adapter 를 구현할지 여부를 결정합니다
사. 요약
우리가 동시에 2 구 플러그와 3 구 소켓을 가지고 있을 때, 항상 플러그 심을 八자 형태로 비틀는 습관이 있습니다. 왜 어댑터를 사지 않을까요?
- 플러그를 파괴할 필요도 없고, 소켓을 파괴할 필요도 없습니다 (코드 수정은 확실히 파괴적일 수 있습니다. 수정을 피함으로써 파괴도 피했습니다)
- 더 중요한 것은: 구매한 어댑터를 친구에게 빌려줄 수 있습니다 (재사용 가능)
아직 댓글이 없습니다