개발자로 후회없는 삶 살기
디자인 패턴 PART.브릿지 패턴 본문
서론
※ 이 포스트는 다음 강의의 학습이 목표임을 밝힙니다.
https://www.inflearn.com/course/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4/dashboard
본론
- 브릿지 패턴 소개
어댑터가 인터페이스와 어댑티를 연결하는 것이었다면 브릿지는 추상적인 것과 구체적인 것을 연결하는 패턴입니다.
=> 구성
1) abstraction : 고차원의 추상적인 로직을 담는 클래스로 이걸 구현하는 클래스를 가집니다.
2) implementation : 구체적인 정보를 담고 있으며, 아주 구체적인 상태, 액션을 담고 있습니다. 이 implemention 자체도 인터페이스로 또 다른 concrete implementaion을 가져서 또 계층을 가지게 됩니다.
추상적인(Abstraction) 부분과 구체적인(Imple) 부분이 있습니다. 성격이 다른 2개를 분리하고 이 둘을 브릿지를 통해 연결해서 사용하는 것입니다. 이렇게 하면 클라이언트는 추상적인 계층만 사용하여 구체적인 것을 간접적으로 사용하며 OCP를 만족할 수 있습니다.
- 코드
챔피언이라는 인터페이스가 있고 챔피언들은 게임 캐릭터들인데 스킨이 있습니다.
public class KDA아리 implements Champion {
계층 구조가 하나라면 챔피언을 구현한 KDA~~의 구현체를 만들 수 있습니다. 이렇게 만들면 나중에 스킬들이 추가되면 겉잡을 수 없는 많은 클래스가 늘어날 것입니다. 따라서 스킬에 해당하는 것은 챔피언 쪽에 남겨두고 스킨, 외관에 대한 것은 분리해서 만든다면 브릿지 패턴으로 코드를 개선할 수 있습니다.
- 패턴 적용하기
=> 패턴 적용 전
1. champion 클래스
public interface Champion {
void move();
void skillQ();
void skillW();
void skillE();
void skillR();
}
move, q스킬, w스킬 처럼 움직이고 스킬을 사용하게 되어있습니다. 이 상태에서 챔피언 별로 스킨과 모양이 바뀌어야 함을 구현체로 해봅니다.
2. KDA아리
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");
}
@Override
public String getName() {
return null;
}
}
그 스킨에 맞는 복장으로 움직이고, 스킨에 따라 다른 모양의 스킬을 발사합니다.
public class PoolParty아리 implements Champion {
@Override
public void move() {
System.out.println("PoolParty move");
}
@Override
public void skillQ() {
System.out.println("PoolParty Q");
}
@Override
public void skillW() {
System.out.println("PoolParty W");
}
@Override
public void skillE() {
System.out.println("PoolParty E");
}
@Override
public void skillR() {
System.out.println("PoolParty R");
}
@Override
public String getName() {
return null;
}
}
풀파티 아리와는 다른 모양과 움직임을 가지고 있습니다.
public class 정복자아리 implements Champion {
@Override
public void move() {
System.out.println("정복자 아리 move");
}
@Override
public void skillQ() {
System.out.println("정복자 아리 Q");
}
@Override
public void skillW() {
System.out.println("정복자 아리 W");
}
@Override
public void skillE() {
System.out.println("정복자 아리 E");
}
@Override
public void skillR() {
System.out.println("정복자 아리 R");
}
@Override
public String getName() {
return null;
}
}
이 상태에서 새로운 챔피언이나 스킬을 추가한다면 구현체를 또 만들고(정복자아리) 정복자에 맞는 모양과 스킬을 가집니다. 이게 하나의 계층 구조로 다양한 특징을 표현하려다보니 커지고, 중복 코드가 많아집니다.
-> App
public class App {
public static void main(String[] args) {
Champion kda아리 = new KDA아리();
kda아리.skillQ();
kda아리.skillR();
}
}
기존 코드를 실행하려면 이런 식으로 실행합니다.
=> 패턴 적용 후 개선
-> DefaultChampion
public class DefaultChampion implements Champion {
private Skin skin;
private String name;
@Override
public void move() {
System.out.println("%s %s move", skin.getName(), this.name);
}
클래스를 하나 만들고 이 클래스가 Champion을 구현하도록 바꿉니다. 그리고 이 클래스는 skin이라는 인터페이스를 사용할 것입니다.
그리고 각 챔피언의 이름을 가집니다. 이 클래스가 챔피언을 구현한 것이기에 똑같이 재정의를 해야하는데 움직일 때는 스킨이름과 챔피언 이름으로 할 것입니다.
-> Skin
public interface Skin {
String getName();
}
이 인터페이스에는 문자열로 스킨에 이름을 리턴하는 메서드가 있습니다.
-> 다시 DefaultChampion
private Skin skin;
private String name;
public DefaultChampion(Skin skin, String name) {
this.skin = skin;
this.name = name;
}
@Override
public void skillQ() {
System.out.printf("%s %s Q\n", skin.getName(), this.name);
}
@Override
public void skillW() {
System.out.printf("%s %s W\n", skin.getName(), this.name);
}
@Override
public void skillE() {
System.out.printf("%s %s E\n", skin.getName(), this.name);
}
@Override
public void skillR() {
System.out.printf("%s %s R\n", skin.getName(), this.name);
}
비슷하게 다른 메서드도 구현하면 됩니다. 그리고 생성자에서 필드로 필요한 스킨과 챔피언 이름을 받도록 합니다.
그럼 이제 챔피언을 늘리고 싶으면 어떻게 하면 될까요? ✅
public class 아리 extends DefaultChampion{
public 아리(Skin skin) {
super(skin, "아리");
}
}
챔피언을 늘리고 싶으면 스킨과 상관없이 늘릴 수 있습니다. 아리라는 챔피언을 만들면 DefaultChampion을 상속 받아서 이제 아리라는 챔피언은 스킨을 받아서 넣어줘야하고 이름은 직접 전달합니다. 아칼리를 만들 때도 동일하게 챔피언만 만들 수 있게 됐습니다.
스킨을 만드려면 어떻게 할까요? ✅
public class PoolParty implements Skin{
@Override
public String getName() {
return "풀파티";
}
}
이제 skin을 구현해서 이름만 전달하면 됩니다. 반대쪽 계층(챔피언)에 영향을 주지 않고 현재 계층(스킨)만 늘릴 수 있습니다. 이게 이 패턴의 핵심입니다.
-> 클라이언트 코드
이런 구조에서 사용할 클라이언트 코드는 어떻게 해야할까요?
public class App {
public static void main(String[] args) {
Champion kda아리 = new 아리(new PoolParty());
kda아리.move();
kda아리.skillE();
}
}
이전 구조에서는 챔피언으로 KDA아리로 스킨과 챔피언을 같이 만들었는데 여기서도 챔피언 인터페이스를 기반으로 코드를 만들 수 있고 다만 챔피언을 만들 때 어떤 스킨을 가지고 있는 아리인지 만듭니다.
스킬을 써보면 잘 나옵니다. 이렇게 하면 클라이언트가 Abstract인 Champion만 사용하고 이 Champion이 implementation에 해당하는 SKin을 사용하는 것입니다.
각각의 스킨은 concrete implementation이 될 것이고 각 챔피언은 refined abstraction이 됩니다.
- 장, 단점
추상적인 것과 구체적인 것을 분리하면서 추상적인 코드만 유지한채 기능을 확장할 수 있습니다.
'[백엔드] > [Java | 학습기록]' 카테고리의 다른 글
디자인 패턴 PART.데코레이터 패턴 (0) | 2023.08.20 |
---|---|
디자인 패턴 PART.컴포짓 패턴 (0) | 2023.08.20 |
디자인 패턴 PART.어댑터 패턴 (0) | 2023.08.18 |
디자인 패턴 PART.프로토타입 패턴 (0) | 2023.08.17 |
디자인 패턴 PART.빌더 패턴 (0) | 2023.08.17 |