구조 관련 : 브릿지 패턴
- 디자인 패턴
- 2023. 12. 7.
들어가기 전
이 글은 인프런 백기선님의 강의를 복습하며 작성한 글입니다.
브릿지 패턴
- GOF : 추상적인 것과 구체적인 것을 분리하여 연결하는 패턴
- 브릿지 패턴은 둘로 나눈 후, 각각의 계층 구조를 생성, 그리고 연결해서 사용한다에 초점
- 어댑터 패턴은 상이한 인터페이스를 연결
- Design Component
- Abstraction : 추상적인 부분. 추상적인 로직만 담고 있음.
- Implmentation : 구체적인 부분. 구체적인 정보를 담고 있음.
- 예시
- 동작만 있는 것 - 상태만 있는 것
- 프론트엔드 - 백엔드
- 클라이언트는 Abstraction만 사용함.
개념적으로 추상적인 부분 / 구체적인 부분을 나누고 각각의 계층 구조를 각각 발전시켜 나갈 수 있다는 장점이 있다. 예를 들어 Refined Abstraction이 추가되어도 Concrete Implmentation에 영향을 주지 않고, 그 반대도 마찬가지다.
디자인 패턴이 필요한 코드
public class KDA아리 implements Champion {
@Override public void move() { System.out.println("KDA 아리 move"); }
@Override public void skillQ() { System.out.println("KDA 아리 Q"); }
@Override public void skillW() { System.out.println("KDA 아리 W"); }
@Override public void skillE() { System.out.println("KDA 아리 E"); }
@Override public void skillR() { System.out.println("KDA 아리 R"); }
}
위 코드가 존재한다고 가정해보자. 이 코드에는 두 가지 속성이 포함되어 있다.
- 챔피언 : 스킬을 쓸 때 챔피언 이름이 나옴.
- 스킨 : 스킬을 쓸 때 스킨 이름이 나옴.
예를 들어 KDA 아칼리, 아리, 카이사가 있을 때 '정복자' 스킨이 추가된다고 가정해보자. 그러면 이를 위해 정복자 아칼리, 아리, 카이사 3개의 클래스를 생성해야한다. 만약 추상적인 부분 / 구체적인 부분을 나눈 후 Delegation 하는 형태였다면 1개의 클래스만 더 만들면 된다.
이렇게 해결하기 위해서는 브릿지 패턴을 이용하면 된다. 추상적인 부분 / 구체적인 부분을 나누고 추상적인 부분이 구체적인 부분을 가져다 쓰는 형태로 바꿔쓰면 된다. 그리고 클라이언트는 '추상적인 부분'에만 의존하면 된다.
챔피언을 추상적인 부분, 스킨을 구체적인 부분으로 고려해 볼 수 있다. 개념상 챔피언이 더 고차원(추상화)이며, 스킨이 더 저차원 (구체적)이기 때문이다.
디자인 패턴 적용
- Abstract (고차원적인 부분)은 DefaultChampion 아래에 분류됨. 새로운 챔피언이 추가되면, DefaultChampion을 상속받으면 됨.
- Implementation (구체적인 부분)은 Skin 아래에 분류됨. 새로운 스킨이 추가되면 스킨 인터페이스를 구현하면 됨.
고차원적, 구체적인 부분은 '코드'의 개념이 아니라 '실생활 개념'으로 봐야한다. 챔피언이 좀 더 고차원적이고, 챔피언에게 적용되는 스킨은 좀 더 구체적인 부분이 될 수 있다. 이런 개념에서 바라봤을 때, 챔피언은 Abstractation이 되고 스킨은 Implementaion이 되는 것이다.
새로운 개념이 들어와서 Abstraction 아래에 계층 구조를 추가 / Implementation에 계층 구조를 각각 추가하더라도 상대쪽에 영향을 미치지 않는다. 예를 들어 Abstraction에 새로운 챔피언이 추가되더라도 Skin 인터페이스쪽에는 어떠한 영향도 미치지 않는다. 이것은 OCP로 볼 수 있으며, 또한 SRP (단일 책임 원칙)을 지켰다고 볼 수 있다. 왜냐하면 챔피언은 챔피언만, 스킨은 스킨에 대한 내용만 처리하기 때문이다.
public class DefaultChampion implements Champion {
private final Skin skin;
private final String name;
public DefaultChampion(Skin skin, String name) {
this.skin = skin;
this.name = name;
}
@Override
public void move() {
System.out.printf(
"%s - %s : move",
skin.getName(),
this.name);
}
...
}
- Abstractaction을 담당하는 DefaultChampion은 다음과 같음
- Champion 인터페이스를 상속받아서 DIP 원칙을 지키도록 함.
- 중복되는 코드의 뼈대를 DefaultChampion에서 만들어주고, 상속 받는 녀석들이 이것들을 재사용하도록 해서 코드의 재사용성을 증가시킨다.
public interface Skin {
String getName();
}
- 이 때, Implementation을 담당하는 Skin은 다음과 같이 구현되어 있음.
public class 아리 extends DefaultChampion{
public 아리(Skin skin) {
super(skin, "아리");
}
}
- DefaultChampion을 상속받은 Concrete 클래스 '아리'는 단순히 생성자만 추가하도록 함.
- 이 때, Skin을 DI 받아서 사용할 수 있도록 구현함.
// 클라이언트 사용 코드.
public class App {
public static void main(String[] args) {
Champion 아리 = new 아리(new KDA());
아리.move();
}
}
- 클라이언트는 아리를 위 코드처럼 사용할 수 있음.
이 때, 클라이언트가 아리, KDA 같은 Concrete 클래스에 의존하는 것처럼 보인다. 그렇지만 이 부분 역시 DI를 이용하면 해결할 수 있게 되는데 Skin, Champion 같은 인터페이스가 이미 존재하기 때문이다. 클라이언트는 전달받는 매개변수로 Skin, Champion만 사용하면 된다. 클라이언트에게 해당 클래스를 주입 해주는 사람이 어떤 구현체를 넣어주기만 하면 된다.
public class App {
private final Champion champion;
public App(Champion champion){
this.champion = champion;
}
public void doSomething(){
this.champion.move();
}
}
예를 들어 클라이언트 코드를 이렇게 작성하면, 클라이언트 코드는 '아리'라는 Redefined Abstraction에 의존하는 것이 아니라 Abstraction 인터페이스에 의존하게 된다.
브릿지 패턴 장/단점
브릿지 패턴은 추상적인 것과 구체적인 것을 '분리'한 후, '연결'하는 패턴임.
- 장점
- 추상적인 코드를 구체적인 코드 변경 없이도 독립적으로 확장할 수 있음.
- 추상적인 코드와 구체적인 코드를 분리할 수 있음.
- 이를 통해 기존 코드 재사용 + 중복 코드를 줄일 수 있다는 장점이 있음.
- 단점
- 계층 구조가 늘어나 클래스 복잡도가 증가할 수 있음.
'디자인 패턴' 카테고리의 다른 글
행동 관련 : 책임 연쇄 패턴 (0) | 2023.12.10 |
---|---|
행동 관련 : 방문자 패턴 (1) | 2023.12.10 |
구조 관련 : 데코레이터 패턴 (0) | 2023.12.04 |
행동 관련 : 이터레이터 패턴 (Iterator Pattern) (0) | 2023.12.02 |
행동 관련 : 중재자 패턴 (Mediator Pattern) (0) | 2023.12.02 |