s_y_130
About SY
s_y_130
전체 방문자
오늘
어제
  • 분류 전체보기
    • JAVA
      • 더 자바 8
      • JAVA
      • JAVA (JVM)
    • Computer Science
      • CS Basic
      • OOP
      • Design Pattern
      • Network
      • HTTP
      • WEB
      • OS
    • DataBase
      • DB theory
      • MySQL
      • Redis
    • Collection Framework
      • 구현
    • Data Structure
      • Linear
      • Non-Linear
    • Algorithm
      • Basic
      • 응용
      • 완전 탐색(Brute Force)
      • 다익스트라
      • Algorithm Problem
    • Spring
      • 스프링 핵심 원리 - 기본편
      • 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
      • 스프링 MVC 2편 - 백엔드 웹 개발 핵심 기술
      • 스프링 DB 1편 - 데이터 접근 핵심 원리
      • 스프링 DB 2편 - 데이터 접근 활용 기술
      • 스프링 핵심 원리 - 고급편
      • 스프링 부트 - 핵심 원리와 활용
      • 재고시스템으로 알아보는 동시성이슈 해결방법
      • 개념
      • 테스트
      • Annotation
      • Error Log
    • TEST
      • 부하 테스트
      • Practical Testing: 실용적인 테스트..
    • JPA
      • 자바 ORM 표준 JPA 프로그래밍
      • 1편- 실전! 스프링 부트와 JPA 활용
      • 2편- 실전! 스프링 부트와 JPA 활용
      • 실전! 스프링 데이터 JPA
      • 실전! Querydsl
      • 개념
    • Open Source
    • Book Study
      • Morden Java in Action
      • Real MySQL 8.0 Vol.1
      • TDD : By Example
    • AWS
      • EC2
    • git
    • AI
      • Machine Learning
      • Deep Learning
      • TensorFlow
      • PyTorch
      • YOLO
      • Data Analysis
      • Ai code Error
      • Numpy
    • MY
    • WEB
      • Django
      • WEB 개념
      • React
      • Maven
    • Python
    • 기초수학

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
s_y_130

About SY

Computer Science/Design Pattern

[Design Pattern] 구조 패턴 - 브릿지 패턴(Bridge Pattern)

2023. 4. 23. 15:52

브릿지 패턴(Bridge Pattern) 이란?


브릿지 패턴은 큰 클래스 또는 밀접하게 관련된 클래스들의 집합을 두 개의 개별 계층구조​(추상화 및 구현)​로 나눈 후 각각 독립적으로 개발할 수 있도록 하는 구조 디자인 패턴이다.

 

extend(상속) 보다 composition(합성) 을 적극 활용한다.

 

같은 말로는 아래와 같이 얘기할 수 있겠다.

 

  • 구현(implementation)으로부터 추상(abstraction) 레이어를 분리하여 이 둘이 서로 독립적으로 변화할 수 있도록 한다." 
  • "구현부에서 추상층을 분리하여 각자 독립적으로 변형이 가능하고 확장이 가능하도록 한다. 즉 기능과 구현에 대해서 두 개를 별도의 클래스로 구현을 한다."
  • "두개의 다른 계층(하나는 추상, 하나는 구현인 서로다른 계층의 커플링을 약화시키며 협력은 가능하도록 하는 패턴"

 

추상화? 구현? 그래서 어쩌란 거지? 간단한 예시를 통해 살펴보도록 하자.

 

 

문제점 - 상속 (extend) 을 통한 접근

 

Circle​(원) 및 Square​(직사각형)​라는 한 쌍의 자식 클래스들이 있는 Shape​(모양) 클래스가 있다고 가정해 보자.

 

이 클래스 계층 구조를 확장하여 색상을 도입하기 위해 Red​(빨간색) 및 Blue​(파란색) 모양들의 자식 클래스들을 만들 계획이다. 그러나 Shape에는 이미 두 개의 자식 클래스(Circle​ 및 Square)가 있으므로 Blue­Circle​(파란색 원) 및 Red­Square​(빨간색 직사각형)​와 같은 네 가지의 클래스 조합을 만들어야 한다.

클래스 조합들의 수는 기하급수적으로 증가할 것이다. 새로운 모양 유형들과 색상 유형들을 추가할 때마다 계층 구조는 기하급수적으로 증가한다.

 

예를 들어, 삼각형 모양을 추가하려면 각 색상별로 하나씩 두 개의 자식 클래스들을 도입해야 한다. 그리고 그 후에 또 새 색상을 추가하려면 각 모양 유형별로 하나씩 세 개의 자식 클래스를 만들어야 한다. 유형들이 많아지면 많아질수록 코드는 점점 복잡해지는 것이 필연적이다.

 

 

해결책 - 사용 (composition) 을 통한 접근

이 문제는 모양과 색상의 두 가지 독립적인 차원에서 모양 클래스들을 확장하려고 하기 때문에 발생한다. 즉, 클래스 상속과 관련된 매우 일반적인 문제라고 할 수 있다.

 

브리지 패턴은 상속에서 객체 합성으로 전환하여 이 문제를 해결하려고 시도한다. 이것이 의미하는 바는 차원 중 하나를 별도의 클래스 계층구조로 추출하여 원래 클래스들이 한 클래스 내에서 모든 상태와 행동들을 갖는 대신 새 계층구조의 객체를 참조하도록 한다는 것이다.

이는 클래스 계층구조의 기하급수적인 성장을 방지하기 위하여 여러 관련 계층구조들로 변환할 수 있다.

 

이 접근 방식을 따르면, 색상 관련 코드를 Red 및 Blue라는 두 개의 자식 클래스들이 있는 자체 클래스로 추출할 수 있다. 그런 다음 Shape 클래스는 색상 객체들 중 하나를 가리키는 참조 필드를 갖는다. 이제 Shape는 연결된 Color 객체에 모든 색상 관련 작업을 위임할 수 있다.

 

이 참조는 Shape 및 Color 클래스들 사이의 브리지​(다리) 역할을 할 것이다. 이제부터 새 Color 들을 추가할 때 Shape 계층구조를 변경할 필요가 없으며 그 반대의 경우도 마찬가지다.

 

 

지금까지 브릿지 패턴의 예제를 통해 문제 파악을 해봤다. 이제는 다이어그램을 통해 이해해보자.

 

브릿지 패턴 클래스 다이어그램

 

 

Abstraction 

- 기능 계층의 최상위 클래스이며 추상 인터페이스를 정의한다.  Implementor에 대한 레퍼런스를 유지한다.
- 구현 부분에 해당하는 클래스를 인스턴스를 가지고 해당 인스턴스를 통해 구현부분의 메서드를 호출한다.

 
RefinedAbstraction

- Abstraction에 의해 정의된 인터페이스를 확장한다.(extends)
- 기능 계층에서 새로운 부분을 확장한 클래스이다.


Implementor 

- 구현 클래스를 위한 인터페이스를 정의한다.
- Abstraction의 기능을 구현하기 위한 인터페이스 정의한다.


ConcreteImplementor 

- Implementor 인터페이스를 구현 즉, 실제 기능을 구현한다.

 

 

브릿지 패턴 예제

 

아래의 다이어그램을 보면 훨씬 더 쉽게 이해 할 수 있다.

 

  • Shape은 기능 클래스 계층
  • Color은 구현 클래스 계층
  • Shape 클래스는 Color 객체를 가지고 있으며 구현부의 브릿지 역할을 함

 

Shape 인터페이

public interface Shape {
    void colorIt();
}

 

 

Rectangle, Circle 클래스

public class Rectangle implements Shape{
    private Color color;

    public Rectangle(Color color){
        this.color = color;
    }

    @Override
    public void colorIt() {
        System.out.println("Rectangle : " + color.fill());
    }
}

public class Circle implements Shape{
    private Color color;

    public Circle(Color color){
        this.color = color;
    }

    @Override
    public void colorIt() {
        System.out.println("Circle : " + color.fill());
    }
}

 

Color 인터페이스

public interface Color {
    public String fill();
}

 

RedColor, BlueColor  클래스

public class RedColor implements Color{
    @Override
    public String fill() {
        return "fill Red Color";
    }
}

public class BlueColor implements Color{
    @Override
    public String fill() {
        return "fill Blue Color";
    }
}

 

BridgeMain 클래스

public class BridgeMain {

    public static void main(String[] args) {
        Shape rectangle = new Rectangle(new RedColor());
        Shape circle = new Circle(new BlueColor());

        rectangle.colorIt();

        System.out.println();

        circle.colorIt();
    }

}

 

브릿지 패턴의 장단점

장점

  • 추상적인 코드를 구체적인 코드 변경 없이도 독립적으로 확장할 수 있다.
    • Shape의 Color 를 더 늘리고 싶다면, Color 를 상속하는 클래스 하나만 더 만들어주어 주입하면 된다.
    • Shape클래스의 colorIt() 메서드에서는 Color 인터페이스를 이용해서 코드를 변경할 일이 없다.
  • 추상적인 코드와 구체적인 코드를 분리할 수 있다.
    • 구조적인 틀을 작성해놓고, 자유롭게 구현 코드를 주입할 수 있다.

단점

  • 계층 구조가 늘어나 복잡도가 증가할 수 있다.
    • 단 하나의 클래스만 만들고 추후에 확장 가능성이 아예 존재하지 않는다면, 오히려 번거로운 작업이 될 수 있다.
    • 처음 보는 개발자는 코드를 파악하는데 어려움을 겪을 수도 있다.

 

 

자바와 스프링에서는 브릿지 패턴을 어떻게 이용하고 있을까?


JDBC

public class JdbcExample {
    public static void main(String[] args) throws ClassNotFoundException {
        Class.forName("org.h2.Driver");

        try (Connection conn = DriverManager.getConnection ("jdbc:h2:mem:~/test", "sa","")) {

            String sql =  "CREATE TABLE  ACCOUNT " +
                    "(id INTEGER not NULL, " +
                    " email VARCHAR(255), " +
                    " password VARCHAR(255), " +
                    " PRIMARY KEY ( id ))";

            Statement statement = conn.createStatement();
            statement.execute(sql);

//            PreparedStatement statement1 = conn.prepareStatement(sql);
//            ResultSet resultSet = statement.executeQuery(sql);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}
  • JDBC 는 DB 벤더에 상관없이 쿼리를 실행시키고, 실행시킨 쿼리에 대한 결과를 받을 수 있다.
  • 실제 DB 에 대한 구체적인 구현은 Class.forName() 에서 호출하는 org.h2.Driver 에 들어있다.
    • Driver 에는 Statement, PreparedStatement, ResultSet 등을 이용하는 코드가 들어있을 것이다.
  • 다른 벤더의 DB 를 사용한다고 해도 JDBC 를 이용하는 코드가 바뀌진 않을 것이다.

 

Slf4j

public class Slf4jExample {
    private static Logger logger = LoggerFactory.getLogger(Slf4jExample.class);

    public static void main(String[] args) {
        logger.info("hello logger");
    }
}
  • Slf4j 와 같은 것을 Logging Facade 라고 부른다.
  • 실제 logger 구현체가 아니라, 로깅에 사용되는 인터페이스이다.
    • 새로운 로깅 구현체를 넣어도 이 코드는 변하지 않는다.
      • log4j2, logback 등 다양한 구현체를 사용할 수 있다.
  • 보는 관점에 따라 브릿지 패턴으로 볼 수 있다.

 

스프링의 MailSender, PlatformTransactionManager

public class BridgeInSpring {
    public static void main(String[] args) {
        MailSender mailSender = new JavaMailSenderImpl();
        PlatformTransactionManager platformTransactionManager = new JdbcTransactionManager();
    }
}
  • 스프링의 PortableServiceAbstraction 에 다양한 예제가 있다.
  • MailSender 에는 JavaMailSenderImpl 과 같은 구현체를 넣을 수 있다.
    • 스프링에서 제공하는 구현체는 JavaMailSenderImpl 하나 뿐이지만, 우리가 얼마든지 새로 MailSender 를 구현해넣을 수 있다.
  • PlatformTransactionManager 는 JdbcTransactionManager, JpaTransactionManager 등을 구현체로 사용할 수 있다.
  • 어떤 구현체를 넣더라도 인터페이스를 이용하기 때문에 기존의 코드가 변하지 않는다.
public class TransactionTemplate extends DefaultTransactionDefinition
        implements TransactionOperations, InitializingBean {

    /** Logger available to subclasses. */
    protected final Log logger = LogFactory.getLog(getClass());

    @Nullable
    private PlatformTransactionManager transactionManager;
  // ...
}
  • TransactionTemplate 에서 PlatformTransactionManager 를 사용하고 있다.

 

 

브릿지 패턴과 어댑터 패턴의 차이

 

두 패턴 모두 Interface의 detail을 감추고자 하며, 구조적인 차이가 없다.

 

하지만 두 패턴은 서로 사용하고자 하는 목적의 차이가 분명하다.

 

  • 어댑터는 어떤 클래스의 인터페이스가 다른 코드에서 기대하는 것과 다를 때(기능은 같은 데, 함수명이 다를 때) 어댑터를 중간에 두어 맞춰주는 것이다.
  • 브릿지는 추상과 구현을 분리하는 것이다.(추상 클래스는 추상 클래스 대로, 구현은 구현 대로 변경해도 서로 영향을 주지 않도록 한다.)
  • 어댑터는 결국 어떤 코드에 맞게끔 기존의 코드를 쓰기 위해 사용되고, 브릿지는 확장성을 고려하여 미리 예상하여 bridge class를 구현해 코드 작성시 사용되어진다.

 

 

 

 

 

 

 


참고

https://www.crocus.co.kr/1537

https://refactoring.guru/ko/design-patterns/bridge

https://jake-seo-dev.tistory.com/397

    'Computer Science/Design Pattern' 카테고리의 다른 글
    • [Design Pattern] 구조 패턴 - 퍼사드 패턴(Facade Pattern)
    • [Design Pattern] 생성 패턴 - 빌더 패턴(Builder Pattern)
    • [Design Pattern] 행동 패턴 - 전략 패턴(Strategy Pattern)
    • [Design Pattern] 구조 패턴 - 프록시 패턴(Proxy Pattern)
    s_y_130
    s_y_130

    티스토리툴바