큰 클래스 또는 밀접하게 관련된 클래스들의 집합을 2개의 개별 계층 구조(추상화 및 구현)로 나눈 후 각각 독립적으로 개발할 수 있도록 하는 구조 디자인 패턴입니다.
부모 추상 클래스가 기본 규칙 세트를 정의하고 구체적인 클래스가 추가 규칙을 추가하고 싶을 경우.
객체에 대한 참조가 있는 추상 클래스가 있고 각 구체적인 클래스에서 정의될 추상 메서드가 있는 경우.

❄︎ Abstraction : 기능 계층의 최상위 클래스. 구현 부분에 해당하는 클래스를 인스턴스를 가지고 해당 인스턴스를 통해 구현 부분의 메서드를 호출
❄︎ RefindAbstraction : 기능 계층에서 새로운 부분을 확장한 클래스
❄︎ Implementor : Abstraction의 기능을 구현하기 위한 인터페이스 정의
❄︎ ConcreteImplementor : 실제 기능을 구현.
Shape(모양) 클래스에 자식 클래스로 Circle과 Square가 있다고 했을 때 이 클래스 계층 구조를 확장하여 색상을 도입하고자 해보자.
그러면 각 모양마다 각각의 색상 구현을 만들어야 한다면 클래스 수가 급격히 늘어납니다. -> 클래스 폭발을 방지.
ex) 모양 유형 : 원, 사각형
색상 유형: 빨강, 녹색
색상 조합을 만들게 되면 RedCircle, GreenCircle, RedSquare, GreenSquare
그래서 브릿지 패턴을 사용해서 모양과 색상을 따로 독립적으로 정의하고, 이를 결합하는 식으로 사용하자는 것입니다.
그러면 분리된 계층으로 둬서 조합의 수만큼 클래스를 만들지 않아도 됩니다.

차원 중 하나를 별도의 클래스 계층구조로 추출하여 원래 클래스들이 한 클래스 내에서 모든 상태와 행동들을 갖은 대신 새 계층구조의 객체를 참조하도록 하는 것입니다.
그림으로 보면 "Color"(interface)이라는 기능을 구현하고 있는 Red과 Blue, 해당 기능을 사용하여 실제 구현을 담당하고 있는 "Shape" 추상 클래스와 추상 클래스의 메서드의 구현을 담당하고 있는 Circle과 Square가 있습니다.
비슷한 예시입니다.

다른 예시를 한번 보겠습니다.

public interface Hunting_Handler {
void Find_Quarry();
void Detected_Quarry();
void attack();
}
public class Hunting_Method1 implements Hunting_Handler {
@Override
public void Find_Quarry() {
System.out.println("물 위에서 찾는다");
}
@Override
public void Detected_Quarry() {
System.out.println("물고기 발견");
}
@Override
public void attack() {
System.out.println("낚아챈다");
}
}
public class Hunting_Method2 implements Hunting_Handler{
@Override
public void Find_Quarry() {
System.out.println("지상에서 찾는다");
}
@Override
public void Detected_Quarry() {
System.out.println("노루 발견");
}
@Override
public void attack() {
System.out.println("물어뜯는다");
}
}
이렇게 Huning_Handler 인터페이스를 상속받아서 실제 기능에 해당하는 부분을 구현했습니다.
이제 Animal이라는 추상 클래스를 통해 Hunting_Handler의 인스턴스를 가지고 각각의 hander를 받아 구현하고 있는 메서드들을 호출합니다.
// 추상 클래스
public abstract class Animal {
private Hunting_Handler huntingHandler;
public Animal(Hunting_Handler huntingHandler) {
this.huntingHandler = huntingHandler;
}
public void Find_Quarry() {
huntingHandler.Find_Quarry();
}
public void Detected_Quarry() {
huntingHandler.Detected_Quarry();
}
public void attack() {
huntingHandler.attack();
}
public void hunt() {
Find_Quarry();
Detected_Quarry();
attack();
}
}
새와 호랑이에 대한 추가적인 기능을 가지도록 해봅니다.
public class Bird extends Animal {
public Bird(Hunting_Handler huntingHandler) {
super(huntingHandler);
}
public void hunt() {
System.out.println("새의 사냥");
Find_Quarry();
Detected_Quarry();
attack();
}
}
public class Main {
public static void main(String[] args) {
Animal tiger = new Tiger(new Hunting_Method2());
Animal bird = new Bird(new Hunting_Method1());
System.out.println("------");
tiger.hunt();
System.out.println("------");
bird.hunt();
}
}
이와 같이 구체 클래스에서 작성한 기능을 원하는 객체와 조합하여 정의할 수 있고 각 동물마다 각각의 사냥 방식에 대한 구현을 일일이 만들지 않아도 됩니다.
| 장점 | 단점 |
|---|---|
| 플랫폼 독립적인 클래스와 앱들을 만들 수 있습니다. -> App의 특정 기능이나 동작이 특정 플랫폼에 종속되지 않고 설계 가능. | 결합도가 높은 클래스에 패턴을 적용하여 코드를 더 복잡하게 만들 수 있습니다. |
| 클라이언트 코드는 상위 수준의 추상화를 통해 작동하며, 플랫폼 세부 정보에 노출되지 않습니다. | |
| 개방/폐쇄 원칙(OCP). 새로운 추상화들과 구현들을 상호 독립적으로 도입할 수 있습니다. | |
| 단일 책임 원칙(SRP). 추상화의 상위 수준 논리와 구현의 플랫폼 세부 정보에 집중할 수 있습니다. |

ex) 110v용 코드를 220v용 콘센트에 꼽을 수 있도록 해주는 중간 어댑터.
서로 다른 인터페이스를 사용할 수 있도록 바꿔줌으로써 기존 코드를 재사용 합니다.
기존 코드를 클라이언트가 사용하는 인터페이스의 구현체로 바꿔주는 패턴입니다.
구성요소
🖇️ https://velog.io/@luda412/Adapter-Pattern
🖇️ https://dev-youngjun.tistory.com/235
🖇️ https://velog.io/@wjd15sheep/%ED%8D%BC%EC%82%AC%EB%93%9C-%ED%8C%A8%ED%84%B4
🖇️ https://velog.io/@winsome_joo/Decorator-Pattern
🖇️ https://refactoring.guru/ko/design-patterns/bridge
🖇️ https://hirlawldo.tistory.com/169
🖇️ https://lktprogrammer.tistory.com/35