2. Adapter 패턴

프로그래밍을 진행하다보면 ‘이미 제공되어 있는 것’과 ‘필요한 것’ 사이에 ‘차이’가 발생하게 되는데 이때 이 ‘차이’를 없애주는 것이 Adapter 패턴이다. (Adapter 패턴은 Wrapper 패턴으로 불리기도 한다.) Adapter 패턴에는 다음과 같은 두 가지 종류가 있다.

클래스에 의한 Adapter 패턴(상속을 사용한 Adapter 패턴)

인스턴스에 의한 Adapter 패턴(위임을 사용한 Adapter 패턴)

예제프로그램은 주어진 문자열을 아래와 같이 표시하는 간단한 것이다.

(Hello)

*Hello*

책에서는 직류변환장치(어댑터)에 빚대어서 메소드 기능과 클래스 다이어그램등을 기술하였지만 포스팅에는 넘어가도록 하겠다 상세한 내용은 책 참고(56~58p)

예제프로그램 (1)  - 상속을 사용한 Adapter 패턴 -

Banner 클래스는 미리 제공되어 있는 클래스라고 가정한다.

//제공되고 있는 것 
public class Banner {
	private String string;

	public Banner(String string){
		this.string = string;
	}

	public void showWithParen(){
		System.out.println("(" + string + ")");
	}

	public void showWithAster(){
		System.out.println("*" + string + "*");
	}

}

Print 인터페이스가 필요로 하는 인터페이스라고 가정한다.

//필요한 것
public interface Print {
	public abstract void printWeak();
	public abstract void printStrong();
}
PrintBanner 클래스

어댑터의 역할을 하는 클래스로서 준비된 Banner클래스를 확장(extends)하여 showWithParen 메소드와 showWithAster 메소드를 상속합니다. 또한 필요한 Print 인터페이스를 구현(implements)해서 printWeak 메소드와 printStrong 메소드를 구현한다.

//교환장치(Adapter)
public class PrintBanner extends Banner implements Print{
	public PrintBanner(String string){
		super(string);
	}
	public void printWeak(){
		showWithParen();
	}
	public void printStrong(){
		showWithAster();
	}

}
Main 클래스

PrintBanner 클래스를 사용해서 Hello라는 문자열을 (약하게), 또는 강하게 표시함

public class Main {
	public static void main(String[] args) {
		Print p = new PrintBanner("hello");
		p.printWeak();
		p.printStrong();
	}
}

예제 프로그램 (2) - 위임을 사용한 Adapter 패턴 -

Main, Banner 클래스는 예제 프로그램(1)과 동일하지만 Print는 인터페이스가 아니고 클래스라고 가정

public abstract class Print {
	public abstract void printWeak();
	public abstract void printStrong();
}
PrintBanner 클래스
public class PrintBanner extends Print{
	private Banner banner;
	public PrintBanner(String string){
		this.banner = new Banner(string);
	}
	public void printWeak(){
		banner.showWithParen();
	}
	public void printStrong(){
		banner.showWithAster();
	}

}

 Adapter 패턴의 등장인물

Target(대상)의 역할 지금 필요한 메소드를 결정합니다. 예제에서는 (1)Print 인터페이스나, (2)Print 클래스에 해당됩니다

Client(의뢰자)의 역할 Target역할의 메소드를 사용해서 일을 처리합니다. 예제에서는 Main 클래스가 해당됩니다.

Adaptee(개조되는 쪽)의 역할 이미 준비되어 있는 메소드를 가지고 있는 역할입니다. 예제에서는 Banner 클래스에 해당합니다. Target과 Adaptee의 역할이 일치할경우에는 Adapter가 필요 없습니다.

Adapter의 역할 Adaptee의 역할의 메소드를 사용해서 Target역할을 만족시키기 위함

위의 내용에 해당하는 클래스 다이어그램 (책 64,65p)

사고 넓히기

Adapter 패턴을 사용하는 이유는 - 이미 존재하고 있는 클래스를 재이용할 경우가 있기 때문이다. 또한 소프트웨어의 버전 업시에 구 버전과의 호환성이 문제가 될경우가 있는데 이때에도 Adapter 패턴을 사용하면 도움이 된다. …… 패턴 사용에 대한 주의점 등은 책을 참고 하고 포스팅에는 생략한다. …… 2-1 예제 프로그램에서는 PrintBanner 클래스의 인스턴스를 작성할때 다음과 같이 Print형의 변수에 대입하고 있습니다.

 Print p = new PrintBanner("Hello");

왜 다음과 같이 PrintBanner형의 변수에 대입하지 않을까요?

 PrintBanner P = new PrintBanner("Hello")

내가 생각한 풀이 : 클래스의 관심사 분리를 위해서라고 생각한다. 책 해설 : ‘Print 인터페이스의 메소드만을 이용한다’는 점을 강조하고 싶었기 때문입니다 … (중략) … ‘PrintBanner클래스의 메소드를 이용하지 않고, Print 인터페이스의 메소드를 이용하고 있다’ 라는 프로그램의 의도가 분명해 집니다. 2-2 java.util.Properties 클래스는

year=2004
month=4
day=21

처럼 키와 그 값의 쌍(property)을 관리하기 위한 것입니다. java.util.Properties 클래스에는 property를 스트림에서 읽거나 스트림에 쓰기 위해 다음과 같은 메소드가 준비되어 있습니다. …void load (InputStream in) throws IOException …void store (OutputStream out, String header) throws IOException : 속성의 집합을 OutputStream에 쓴다. header는 코멘트 문자열. Adapter 패턴을 소용해서  property의 집합을 파일에 보존하는 FlieProperties 클래스를 만들어야합니다. 여기에서 property의 집합을 파일에 저장하는 메소드는 FileIO 인터페이스(Target 역할)에서 서언하고,  FlieProperties클래스는 이 FlieIO 인터페이스를 구현한다고 가정합니다. 실행 전 flie.txt 밎 실행 후 newflie.txt는 같습니다. FileProperties 클래스가 java,.util.Properties 클래스의 메소드에 대해서 몰라도 FileIO 인터페이스의 메소드만 알고 있다면 property를 취급할 수 있습니다.

FileIO 인터페이스
import java.io.*;

public interface FileIO {
	public void readFromFile(String filename) throws IOException;
	public void writeToFile(String fliename) throws IOException;
	public void setValue(String key, String value);
	public String getValue(String key);
}
Main 클래스
public class Main {
	public static void main(String[] args) {
		FileIO f = new FileProperties();
		try{
			f.readFromFile("file.txt");
			f.setValue("year", "2004");
			f.setValue("month", "4");
			f.setValue("day", "21");
			f.writeToFile("newfile.txt");
		}catch(IOException e){
			e.printStackTrace();
		}
	}
}
입력파일(file.txt)
year=1999
출력파일 예(newfile.txt)
#written by FileProperties
#Sat Aug 02 14:11:48 KST 2014
day=21
year=2004
month=4

내가 생각한 풀이 :

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Properties;

public class FileProperties extends Properties implements FileIO {

	@Override
	public void readFromFile(String filename) throws IOException {
		InputStream is = new FileInputStream(filename);	
		load(is);
	}

	@Override
	public void writeToFile(String fliename) throws IOException {
		OutputStream os = new FileOutputStream(fliename); 
		store(os,"written by FileProperties");
		os.close();
	}

	@Override
	public void setValue(String key, String value) {
		setProperty(key, value);
	}

	@Override
	public String getValue(String key) {
		return getProperty(key);
	}

책 해설 :

import java.io.*;
import java.util.*;

public class FileProperties extends Properties implements FileIO {
	public void readFromFile(String filename) throws IOException {
		load(new FileInputStream(filename));
	}
	public void writeToFile(String filename) throws IOException {
		store(new FileOutputStream(filename), "written by FileProperties");
	}
	public void setValue(String key, String value){
		setProperty(key, value);
	}
	public String getValue(String key){
		return getProperty(key,"");
	}

}