추상화와 구현의 분리
추상화는 이론적인 개념입니다. 각종 정책이 추상화에 해당하죠. 수학이나 물리학적으로 보면, 이론이 먼저 나오고 그 이론이 맞는지 검증을 합니다. 이때 이론이 추상화이고, 검증하는 절차가 구현입니다. 전자제품을 구매했을 때 설명서도 같이 동봉됩니다. 설명서가 추상화이고, 전자제품을 사용하는 것이 구현에 해당합니다.
1988년 "Object-Oriented Software Construction"이라는 책에서 Bertrand Meyer에 의해 객체지향 설계에서 "핸들/본체(Handle/Body)" 패턴으로 알려진 비슷한 개념이 논의된 바가 있습니다. 이를 GOF의 책에서 공식 패턴으로 소개되었습니다.
브리지 패턴은 특히 그래픽 시스템 개발에서 다양한 플랫폼과 렌더링 시스템을 지원해야 하는 필요성에서 발전했습니다. 여러 시스템(Windows, Mac.. 등)에서 동작하는 그래픽 애플리케이션을 개발하는 과정에서 추상화와 구현을 분리하는 패턴의 유용성이 확인되었습니다.
브리지 패턴은 추상화(Abstraction), 정제된 추상화(Refined Abstraction), 구현부(Implementor), 구체적 구현(Concrete Implementor)으로 이루어집니다. 추상화는 상위 수준의 제어 논리를 정의하고, 구현부에 대한 참조를 유지하고, 정제된 추상화는 추상화를 확장하여 더 세부적인 기능 제공합니다. 구현부는 하위 수준의 인터페이스 정의하고, 구체적 구현은 구현부 인터페이스의 실제 구현합니다.
// 구현부(Implementor) 인터페이스 - 색상을 담당
interface Color {
String fill();
}
// 구체적인 구현(Concrete Implementor) - 빨간색
// 구현부에서 정의한 부분을 실제 구현합니다.
class Red implements Color {
@Override
public String fill() {
return "빨간색";
}
}
// 구체적인 구현(Concrete Implementor) - 파란색
class Blue implements Color {
@Override
public String fill() {
return "파란색";
}
}
// 추상화(Abstraction) - 도형을 담당
// 도형이라는 논리적인 추상화를 정의합니다.
abstract class Shape {
protected Color color; // 브리지: 구현부에 대한 참조
public Shape(Color color) {
this.color = color;
}
// 도형은 그려져야 합니다.
abstract public String draw();
}
// 구체적인 추상화(Refined Abstraction) - 원
// 원의 구체적인 추상화를 정의합니다.
class Circle extends Shape {
public Circle(Color color) {
super(color);
}
@Override
public String draw() {
return color.fill() + " 원을 그립니다";
}
}
// 구체적인 추상화(Refined Abstraction) - 사각형
class Square extends Shape {
public Square(Color color) {
super(color);
}
@Override
public String draw() {
return color.fill() + " 사각형을 그립니다";
}
}
// 클라이언트 코드
// 실제 사용하는 예시입니다.
public class Main {
public static void main(String[] args) {
// 빨간 원
Shape redCircle = new Circle(new Red());
// 파란 사각형
Shape blueSquare = new Square(new Blue());
System.out.println(redCircle.draw()); // 출력: 빨간색 원을 그립니다
System.out.println(blueSquare.draw()); // 출력: 파란색 사각형을 그립니다
}
}
위 코드에서 구현부(Color)는 색상을 처리하는 인터페이스와 구현체(Red, Blue)로 구성되고, 추상화(Shape)는 도형을 처리하는 추상 클래스와 구현체(Circle, Square), 브리지로 Shape 클래스가 Color 참조를 가지고 있습니다.
만약 브리지 패턴을 사용하지 않았다면, 모든 조합(RedCircle, BlueCircle, RedSquare, BlueSquare)마다 클래스를 만들어야 하지만, 브리지 패턴으로 도형과 색상이 독립적으로 확장 가능합니다. 예를 들어 새로운 색상(Green)이나 도형(Triangle)을 추가하더라도 기존 코드를 변경할 필요 없이 새로운 클래스만 추가하면 됩니다.
이처럼 구현부와 추상화를 연결하는 패턴이 브리지 패턴입니다. 좀 더 쉽게 설명해서, 가전제품과 리모컨이 있을 경우 가전제품은 구현체이고, 리모컨이 추상화에 해당합니다. 리모컨은 버튼들만 있고, 눌렀을 때 어떤 게 동작한다라는 논리만 있을 뿐입니다. 이를 실제로 구현하는 건 TV, 에이컨, 오디오 등에 해당합니다. 리모컨 하나로 TV, 에어컨, 오디오를 컨트롤할 수 있게 해주는 방식이 브리지 패턴입니다. 브리지 패턴을 적용하지 않는 다면 TV 리모컨, 에어컨 리모컨, 오디오 리모컨이 각각 별도로 있어야 합니다.
조금 더 예시를 들면, 음식점의 한식, 중식, 양식은 음식 구현체에 해당합니다. 이런 음식을 주문하기 위해서는 매장에서 직접 주문하거나, 전화 주문, 배달앱으로 주문합니다. 이런 주문 방법이 추상화에 해당합니다. 브리지 패턴을 통해 매장에서 한식, 중식, 양식을 다 주문할 수 있고, 전화, 배달앱 또한 한식, 중식, 양식을 다 주문할 수 있습니다. 브리지 패턴을 적용하지 않는다면 매장 주문용 한식, 매장 주문용 중식, 전화 주문용 한식 등등 각각 연결해줘야 합니다.
매장에서 메뉴를 보고 주문하는 것은 우리에게 당연한 일이지만, 컴퓨터 시스템의 입장에서는 그렇지 않습니다. 매장에서 어떤 것을 주문할 수 있고, 전화로 어떤 것을 주문할 수 있는지 등을 각각 만들어 줘야 합니다. 매장용 한식, 매장용 중식, 매장용 양식, 전화주문 한식, 전화주문 중식… 등을 각각 만들면 비효율적일 수밖에 없습니다. 이를 추상화(주문방법)와 구현(음식)을 각각 만들어 서로 연결(브리지)해주는 패턴을 이용하면 더 효율적인 로직이 가능합니다.
음식을 예로 들었지만, 주문방법에 해당하는 추상화는 다른 곳에서도 충분히 활용이 가능합니다. 액세서리도 매장 주문, 전화 주문, 배달앱으로도 주문할 수 있는 추상화입니다. 추상화를 어느 구현체와 연결(브리지)하느냐에 따라 다양한 프로그래밍이 가능합니다.
브리지 패턴의 핵심은 유연성(한쪽을 변경해도 다른 쪽에 영향이 최소화), 확장성(새로운 추상화나 구현체를 쉽게 추가), 조직적 분업(추상화 팀과 구현 팀이 독립)에 있습니다. 사회에서도 이런 '브리지' 개념은 중요합니다. 정책(추상화)과 실행(구현)이 분리되어 있되 효과적으로 연결되어 있을 때, 사회 시스템은 더욱 유연하고 효율적으로 작동할 수 있습니다.