브릿지 (Bridge) 패턴
- 커다란 클래스 혹은 긴밀하게 연관된 클래스를 추상 부분과 구현 부분으로 쪼개어 나눈다.
- 두 부분이 독립적으로 개발될 수 있다.
extend
보다composition
을 적극 활용한다.
다이어그램으로 살펴보기
현실 예제
- 컵을 판매하는 회사가 있다.
- 컵의 재질은 플라스틱, 유리, 종이 등이 존재한다.
- 컵의 색상은 빨간색, 파란색, 노란색, 초록색 등이 존재한다.
- 컵을 클래스화 시켜서 자바 프로그램에 녹여넣고 싶은데, 어떤 방식이 좋을까?
상속 (extend
) 을 통한 접근
- 먼저
Cup
클래스를 만들었다. - 컵의 재질을 표현하기 위해서
Material
인터페이스를 추가했다. Material
을 상속하는Plastic
,Glass
,Paper
등을 만들었다.- 이를 이용해
Cup
에Material
을 상속시켜PlasticCup
,GlassCup
,PaperCup
등을 만들었다. - 이번엔 컵의 색상을 표현하기 위해서
Color
인터페이스를 추가했다. Color
를 상속시켜Red
,Green
,Blue
등을 만들었다.PlasticCup
에Red
를 상속시킨RedPlasticCup
,Green
을 상속시킨GreenPaperCup
등을 성공적으로 만들어냈다.- 시간이 지날수록 새로운 색이 추가되고 새로운 재질의 컵이 탄생했다.
- 상속 클래스가 수십개가 탄생했고, 새로운 재질과 색상이 나올 때마다 컵을 추가해야 한다.
사용 (composition
) 을 통한 접근
Cup
클래스를 만드는 것까지는 동일하다.Cup
에Material
과Color
인터페이스를 필드로 추가했다.Material
과Color
를 인자로 받는 생성자만 만들어주었다.- 이제 어떤 재질의 어떤 색상의 컵을 사용하더라도 더 이상 클래스를 추가할 필요는 없다.
사용을 통한 접근이 갖는 의미
Bridge
패턴은 사용을 통한 접근을 이용해 문제를 해결하려 한다.- 클래스를 별도의 계층으로 추출함으로써, 원래 클래스가 한 클래스 내의 모든 상태 및 동작을 갖지 않고 새 계층의 객체를 참조한다.
코드 예제
- 멀티플랫폼을 지원하는 GUI 도구를 만들려고 한다.
- Mac 운영체제와 Windows 운영체제의 버튼을 만들어야 한다.
- 버튼의 요구사항은 아래와 같다.
- 버튼에는 색상이 들어갈 수 있다.
- 버튼을 클릭하면 실행될 이벤트를 줄 수 있다.
Button
public interface Button {
void onClick();
}
Button
은 인터페이스로 클릭하면 실행되는onclick
이벤트를 가지고 있다.
Color
public interface Color {
String getColorCode();
}
Color
도 인터페이스로 색상의 색상코드를 이용해 버튼의 색을 칠한다.
Red
public class Red implements Color {
@Override
public String getColorCode() {
return "FF0000";
}
}
- 빨간색을 구현한 것이다.
Blue
public class Blue implements Color {
@Override
public String getColorCode() {
return "0000FF";
}
}
- 파란색을 구현한 것이다.
MacButton
public class MacButton implements Button{
private final Color color;
public MacButton(Color color) {
this.color = color;
}
@Override
public void onClick() {
System.out.println("맥 버튼 클릭, 색상 컬러: " + color.getColorCode());
}
}
- 맥OS 에 사용되는 버튼을 만들었다.
- 생성자에서
Color
를 받고,onClick
이벤트는 직접 구현하면 된다.
- 생성자에서
WindowsButton
public class WindowsButton implements Button{
private final Color color;
public WindowsButton(Color color) {
this.color = color;
}
@Override
public void onClick() {
System.out.println("윈도우즈 버튼 클릭, 색상 컬러: " + color.getColorCode());
}
}
- windows 에도 동일한 버튼을 만들어보았다.
클라이언트에서 사용하기
public class Client {
public static void main(String[] args) {
Button macButton = new MacButton(new Red());
Button windowsButton = new WindowsButton(new Blue());
macButton.onClick(); // 맥 버튼 클릭, 색상 컬러: FF0000
windowsButton.onClick(); // 윈도우즈 버튼 클릭, 색상 컬러: 0000FF
}
}
- 클라이언트는 두가지 플랫폼의 버튼을 자유로운 색상으로 마음껏 만들 수 있다.
- 더 확장하여
OS
인터페이스를 만들어 필드에 넣을 수 있다. onClick
도Event
라는 인터페이스를 상속하는onClickEvent
인터페이스를 만들어 상속하고 필드로 사용할 수도 있다.
예제에서 유의깊게 봐야하는 점
- 단순히
WindowsButton
과MacButton
이라는 클래스를 만들어 필드를 생성하고 구현한 것이 아니라, 공통으로 구현에 필요한 부분을 추상화하여 나누었다. - 어디든
Button
타입이 필요한 곳이면,WindowsButton
인지MacButton
인지는 무관하게 두 구현체 중 아무거나 올 수 있게 되었다. - 최악의 케이스를 상정해보자면,
WindwosRedButton
,WindowsBlueButton
,MacRedButton
,MacBlueButton
과 같은 개별 클래스들이 탄생할 수도 있었다. - 다이어그램과 비교하자면, 예제의
Button
이Abstraction
이 될 수 있다.Windows Button
은Refined Abstraction
이다.Color
인터페이스는Implementation
에 해당하고,Red
와 같은 실제 색상은Concrete Implementation
에 해당한다.
브릿지 패턴의 장단점
장점
- 추상적인 코드를 구체적인 코드 변경 없이도 독립적으로 확장할 수 있다.
WindowsButton
의Color
를 더 늘리고 싶다면,Color
를 상속하는 클래스 하나만 더 만들어주어 주입하면 된다.WindowsButton
클래스의onClick()
메서드에서는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
를 사용하고 있다.
반응형
'Java > 자바 디자인 패턴' 카테고리의 다른 글
데코레이터 패턴 (Decorator Pattern) 이란? (0) | 2023.02.25 |
---|---|
컴포지트 패턴 (Composite Pattern, 컴포짓 패턴) 이란? (0) | 2023.02.20 |
자바 디자인 패턴, 객체 생성 관련 패턴 (Object Creational Patterns) 이란? (0) | 2023.02.17 |
어댑터 패턴 (Adapter Pattern) 이란? (1) | 2023.01.29 |
프로토타입 패턴 (Prototype Pattern) 이란? (2) | 2023.01.28 |