반응형
의도
- 구현에서 추상을 분리하여, 이들이 독립적으로 다양성을 가질 수 있도록 한다.
동기
- 하나의 추상적 개념이 여러 가지 구현으로 구체화될 수 있을 때, 대부분은 상속을 통해서 이 문제를 해결한다.
- 추상 클래스로 추상적 개념에 대한 인터페이스를 정의하고, 구체적인 서브클래스들에서 서로 다른 방식으로 이들 인터페이스를 구현한다.
- 그러나 상속은 구현과 추상적 개념을 영구적으로 종속시키기 때문에, 추상적 개념과 구현을 분리해서 재사용하거나 수정•확장하기가 쉽지 않다.
- 예) Window를 추상적 개념으로 보고 상속을 통해 구현하게 되는 경우
- IconWindow와 같이 새로운 개념의 Window를 만든다고 하면, 이 새로운 개념의 Window가 XWindow, PMWindow에 모두 동작하게 하려면 XIconWindow, PMIconWindow와 같은 새로운 서브 클래스를 구현해야 한다. 또 앞으로 새로운 종류의 Window가 추가된다면 해당하는 플랫폼을 지원하는데 필요한 클래스를 계속 개발해야한다.
- 또한 이 서브 클래스들을 구현하는 코드는 플랫폼에 종속된다. XIconWindow는 XWindow, PMIconWindow는 PMWindow의 구현에 묶인다. 이는 해당 응용프로그램을 서로 다른 플랫폼에 이식하기 어려움을 의미한다.
- 가교 패턴은 추상적 개념에 해당하는 클래스 계통(Window)과 구현에 해당하는 클래스 계통(WindowImp)을 분리함으로써 이러한 문제를 해결할 수 있다.
- Window 클래스와 WindowImp 클래스 간의 관련성을 가리켜 가교(bridge)라고 한다.
활용성
- 추상적 개념과 이에 대한 구현 사이의 지속적인 종속 관계를 피하고 싶을 때
- 런타임에 구현 방법을 선택하거나 구현 내용을 변경하고 싶을 때
- 추상적 개념과 구현 모두가 독립적으로 서브클래싱을 통해 확장되어야 할 때
- 가교 패턴은 개발자가 구현을 또 다른 추상적 개념과 연결할 수 있게 할 뿐 아니라, 각각을 독립적으로 확장 가능하게 한다.
- 추상적 개념에 대한 구현 내용을 변경하는 것이 다른 관련 프로그램에 아무런 영향을 주지 않아야 할 때
- 추상적 개념에 해당하는 클래스를 사용하는 코드들은 구현 클래스가 변경되었다고 해서 다시 컴파일되지 않아야 한다.
- 클래스 계통에서 클래스 수가 급증하는 것을 방지하고자 할 때
- 가교 패턴을 사용하지 않은 IconWindow 클래스에서는 새로운 종류의 Window가 추가될 때 마다 해당 플랫폼을 지원하기 위해 서브 클래스를 계속 개발 해야한다.
- 여러 객체들에 걸쳐 구현을 공유하고자 하며, 이런 사실을 사용자 쪽에 공개하고 싶지 않을 때
결과
- 인터페이스와 구현 분리
- 추상적 개념에 대한 어떤 방식의 구현을 택할지가 런타임에 결정될 수 있다. 즉, 런타임에 어떤 객체가 자신의 구현을 수시로 변경할 수 있다.
- 추상과 구현의 분리는 컴파일 타임 의존성을 제거할 수 있다. 구현을 변경하더라도 추상적 개념에 대한 클래스를 다시 컴파일할 필요가 없고, 추상적 개념 클래스와 관련된 다른 코드도 다시 컴파일 필요가 없다.
- 계층화(layering)도 가능해진다.
- 시스템의 상위 수준 영역에서는 Abstraction과 Implementor만 알면 된다.
- 향상된 확장성
- Abstraction과 Implementor를 독립적으로 확장할 수 있다.
- 구현 세부 사항을 사용자에게서 숨긴다.
- 상세한 구현 내용을 사용자에게서 은닉할 수 있다.
협력 방법
- Abstraction 클래스가 사용자 요청을 Implementor 객체에 전달한다.
구조
실제 구현 구조
소스코드
//Abstraction
public abstract class Animal {
private Behavior behavior;
protected Animal(Behavior behavior) {
this.behavior = behavior;
}
public abstract void info();
public void move() {
this.behavior.move();
}
public void attack() {
this.behavior.attack();
}
}
//RefindAbstraction
public class Crow extends Animal {
protected Crow(Behavior behavior) {
super(behavior);
}
@Override
public void info() {
System.out.println("까마귀");
}
public void crowMove() {
super.move();
}
public void crowAttack() {
super.attack();
}
}
//RefindAbstraction
public class Croaker extends Animal {
protected Croaker(Behavior behavior) {
super(behavior);
}
@Override
public void info() {
System.out.println("조기");
}
public void croakerMove() {
super.move();
}
public void croakerAttack() {
super.attack();
}
}
//Implementor
public interface Behavior {
void move();
void attack();
}
//ConcreteImplementorA
public class BirdBehaviorImpl implements Behavior {
@Override
public void move() {
System.out.println("날기");
}
@Override
public void attack() {
System.out.println("쪼기");
}
}
//ConcreteImplementorB
public class FishBehaviorImpl implements Behavior {
@Override
public void move() {
System.out.println("헤엄치기");
}
@Override
public void attack() {
System.out.println("깨물기");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Croaker(new FishBehaviorImpl());
animal.info();
animal.move();
animal.attack();
System.out.println("-----------------------------------");
animal = new Crow(new BirdBehaviorImpl());
animal.info();
animal.move();
animal.attack();
}
}
결과
관련 패턴
- 추상 팩토리 패턴을 이용해서 특정 가교를 생성하고 복합할 수 있도록 한다.
- 적응자 패턴은 서로 관련 없는 클래스들이 함께 동작하게 만드는 쪽에 특화되어있다. 이 패턴은 대개 각 클래스의 설계가 끝난 후에 적용된다. 그러나 가교 패턴은 설계 단계 초기에 투입되어 추상화 및 구현이 독립적으로 다양화되도록 만드는 데 사용된다.
[참고자료]
리처드 헬름, 랄프 존슨, 존 블리시디스, 『GoF의 디자인 패턴 : 재사용성을 지닌 객체지향 소프트웨어의 핵심요소』, 김정아 번역, 프로텍미디어(2015)
http://www.cs.unc.edu/~stotts/GOF/hires/pat4bfso.htm
반응형