<aside> 📚 • Java IO 클래스 라이브러리의 기본 설계 사상을 분석해 데커레이터 패턴을 알아보자
</aside>
Java IO 클래스 라이브러리
Java IO 클래스 라이브러리 활용 예시
InputStream in = new FileInputStream("test.txt");
InputStream bin = new BufferedInputStream(in);
byte[] data = new byte[128];
while (bin.read(data) != -1) { ... }
FileInputStream
객체를 생성해 InputStream
추상클래스 객체에 할당한 다음, BufferedInputStream
객체에 이를 전달하여 파일을 읽고 있다.
이렇게 사용할 바에는 FileInputStream
클래스를 확장하여 BufferedFileInputStream
클래스를 설계하는 것이 더 낫지 않을까?
InputStream in = new BufferedFileInputStream("test.txt");
byte[] data = new byte[128];
while (bin.read(data) != -1) { ... }
하지만 InputStream
의 하위 클래스가 너무 많기 때문에, 이 모든 하위에 Buffered
로 시작하는 클래스를 추가하기에는 Java IO 클래스 라이브러리가 너무 커질 수 있다.
상속 기반의 설계는 좋은 해결책이 되지 못한다.
상속 구조가 너무 복잡하다면 상속 관계를 합성
관계로 바꾸어 문제를 해결할 수 있다.
합성
: 기존 클래스를 상속으로 확장하는 대신 필드로 클래스의 인스턴스를 참조하게 만드는 설계public class BufferedInputStream extends InputStream {
protected volatile InputStream in;
protected BufferedInputStream(InputStream in) {
this.in = in;
}
...
}
public class DataInputStream extends InputStream {
protected volatile InputStream in;
protected DataInputStream(InputStream in) {
this.in = in;
}
}
단순 합성과 데커레이터 패턴의 차이
데커레이터 클래스가 원본 클래스와 동일한 상위 클래스를 상속하기 때문에 원본 클래스 내에 여러개의 데커레이터 클래스를 중첩할 수 있다.
InputStream in = new FileInputStream("test.txt");
Inputstream x = new BufferedInputstream(in); // 첫번째 중첩
DatainputStream y = new DataInputstream(x); // 두번째 중접
int data = y.readInt;
데커레이터 클래스의 기능 : 원본 클래스의 기능 향상
정리
<aside> 📚 • 어댑터 패턴의 두가지 방식인 클래스 어댑터와 객체 어댑터 • 어댑터 패턴의 5가지 응용 방법 • SLF4J 로깅 프레임워크 분석을 통해 알아보는 어댑터 패턴의 활용
</aside>
클래스 어댑터
: 상속 관계 사용 / 객체 어댑터
: 합성 관계 사용
ITarget
: 변환할 대상 인터페이스Adaptee
: ITarget
과 호환되지 않는 원본 인터페이스 그룹Adapter
: Adaptee
를 ITarget
인터페이스와 호환 가능한 인터페이스로 변환하는 클래스Adaptee
인터페이스가 많지 않다면 두 방식 중 어느 것을 사용해도 무방하다.Adaptee
인터페이스가 많지만 Adaptee
와 ITarget
인터페이스의 정의가 대부분 같다면 Adapter
클래스가 상위 클래스인 Adaptee
의 인터페이스를 재사용할 수 있으므로 클래스 어댑터
를 사용하는 것이 좋다.