Java/Design Pattern

[디자인 패턴] 구조 패턴 - 브릿지 패턴 (Bridge Patterns)

민돌v 2023. 2. 24. 19:00
728x90
(인프런) 코딩으로 학습하는 GoF의 디자인 패턴 - 백기선, 강의를 보고 정리한 글입니다.
코드는 GitHub 에 있습니다

 

#1. 객체 생성 관련 패턴

#2. 구조 관련 패턴

 

#3. 행동 관련 패턴

 

 

 

 


✔️ 브릿지 패턴

  • 추상적인 것과 구체적인 것을 분리하여 연결하는 패턴
  • 브릿지 패턴의 중점은 "기능 클래스 계층" 과 "구현 클래스 계층" 의 분리라고 합니다.
    • 기능 클래스 계층이란 - 기본기능을 가지고 있는 부모 클래스를 상속받아 새로운 기능이 추가된 하위클래스 (상속)
    • 구현 클래스 계층이란  - 기능을 정의한 추상 클래스(or 인터페이스) 의 기능을 구현한 하위 클래스
  • 클래스 계층이 1개여서 기능과 구현의 클래스 계층이 뭉쳐있으면 새로운 기능이 추가될 수록 계층구조가 무거워지고, 이로인해 1개의 클래스의 권한이 너무 커지고, 의존하는 문제가 발생하기 때문에, 이 2가지 계층을 분리해서 관리하고 2 계층을 연결하기위한 패턴이 브릿지 패턴입니다.

 

 

✔️ 브릿지 패턴 구조 및 특징

클라이언트는 구현체를 직접 사용하지 않고 추상적인 인터페이스를 사용한다.
  • 하나의 계층구조일 때 보다 2개의 계층구조로(기능, 구현) 나누었을 떄 독립적인 계층 구조로 발전 시킬 수 있습니다. 

 

강의에서 2가지 계층구조, "추상적인 것" 과  "구체적인 것"을 분리하기 위해서는 상속(Inheritance)이 아닌, 합성 (Composition) 을 사용해야 한다고 말합니다.

📌 구성(합성) (Composition) : 블랙박스 재사용(black-box reuse)

- 클래스 상속의 대안입니다. 다른 객체를 여러 개 붙여서 새로운 기능 혹은 객체를 구성하는 것입니다.
- 구성하려는 객체의 인터페이스를 명확하게 정의하여야 합니다.
- 이런 방식은 객체의 내부는 공개되지 않고 인터페이스를 통해서만 재사용되기 때문에 블랙박스 재사용이라 합니다.

 

[ 상속이란 (Inheritance)]

  • 상속은 상위 클래스에 중복(공통) 로직을 구현해두고, 자식 클래스에서 이를 재사용하는 방법입니다.
  • 흔히 상속을 Is-a 관계라고 부립니다.
  • 아래처럼 요리사와 사람 클래스가 존재할 때 요리사는 사람이므로 이러한관계를 is-a 관계라고 합니다.
public class Person {

    public void walk() {
        System.out.println("걷는다");
    }

    public void talk() {
        System.out.println("말한다");
    }
}

public class Chef extends Person {
    
}

 

[ 합성이란 (Composition) ]

  • 합성은 중복되는 로직들을 가지는 객체를 구현하고, 이를 주입받아 호출해서 재사용하는 방법입니다.
  • 상속과 유사하지만, 한 클래스가 다른 클래스의 객체를 포함하는 관계이기 때문에, 흔히 합성을 Has-a 관계라고 부릅니다.
  • 아래처럼 요리사가 음식의 가격을 계산해야한다고 했을 때, 요리사는 자신이 만든 음식들을 가지고 있고 사람에 대한 특징또한 가지고 있기 때문에 Has-a 라고 합니다.
public class Chef {

    private Person person;
    private List<Food> foodList;
    
    public Chef(Person person, List<Food> foodList) {
    	this.person = person;
        this.foodList = foodList;
    }

    public int calculatePrice() {
        return foodList.stream()
            .mapToInt(v -> v.getPrice())
            .sum();
	}
}

public class Person {

    public void walk() {
        System.out.println("걷는다");
    }

    public void talk() {
        System.out.println("말한다");
    }
}

 

 

✔️ 브릿지 패턴 구현하기

이제 코드로 봅시다!
예제는 기본적인 skill 과 행동을 가지고 있는 Champion 객체를 구현합니다.
이 챔피온에는 특정한 Skin 을 입힐 수 있습니다.

 

Champion.class

  • 먼저 공통적인 행동(기능) 을 정의해둔 챔피온 인터페이스를 정의합니다.
public interface Champion {

    void move();
    void skillQ();
    void skillW();
    void skillE();
    void skillR();
}

Skin.class

  • skin 클래스는, 간단하게 어떤 스킨인지 공통 기능을 정의해둔 인터페이스 입니다.
public interface Skin {

    String getName();
}


//Skin 구현체들
public class PoolParty_skin implements Skin{

    @Override
    public String getName() {
        return "PoolParty Skin";
    }
}

public class KDA_skin implements Skin{

    @Override
    public String getName() {
        return "KDA Skin";
    }
}

 

DefaultChampion.class

  • DefaultChampion 은 Champion 의 기능을 구현한 1개의 구현 클래스 계층입니다.
  • DefaultChampion 은 Skin을 외부에서 주입받아 멤버변수로 가지고 있습니다.
  • Skin 은 기능을 정의만한 인터페이스 이므로, 외부에서 Skin 을 구현한 또다른 구현 계층 클래스를 주입받아야합니다.
  • 이렇게 외부에서 주입받는 Skin 필드가 두 클래스 계층을 연결하는 '다리 (Bridge)' 가 됩니다.
public class DefaultChampion implements Champion {

    private Skin skin;
    private String name;

    public DefaultChampion(Skin skin, String name) {
        this.skin = skin;
        this.name = name;
    }

    @Override
    public void move() {
        System.out.printf("%s %s move\n", skin.getName(), this.name);
    }

    @Override
    public void skillQ() {
        System.out.printf("%s %s Q skill \n", skin.getName(), this.name);
    }

    @Override
    public void skillW() {
        System.out.printf("%s %s W skill \n", skin.getName(), this.name);
    }

    @Override
    public void skillE() {
        System.out.printf("%s %s E skill \n", skin.getName(), this.name);
    }

    @Override
    public void skillR() {
        System.out.printf("%s %s R skill \n", skin.getName(), this.name);
    }
}

 

Client 코드

  • 이렇게 브릿지 패턴으로 계층구조를 분리한 후 연결하였으니, 서로다른 계층구조에서 추가적인 요구사항에 따라 확장이 가능해졌고
  • 클라이언트에서 사용할 계층구조를 주입해주어 2 계층구조를 연결해주는 구조가 완성되었습니다.
public class App {

    public static void main(String[] args) {

        Champion kda아리 = new 아리(new KDA_skin());
        kda아리.move();
        kda아리.skillQ();

        Champion poolParty아리 = new 아리(new PoolParty_skin());
        poolParty아리.move();
        poolParty아리.skillR();

    }
}


public class 아리 extends DefaultChampion {

    public 아리(Skin skin) {
        super(skin, "아리");
    }
}

public class 아칼리 extends DefaultChampion {

    public 아칼리(Skin skin) {
        super(skin, "아칼리");
    }
}


✔️ 브릿지 패턴을 적용하지 않았을 때

  • 브릿지 패턴을 적용하지 않았을 때 계층구조가 어떻게 무거워지는지 살펴봅시다.
  • 단순하게 Champion 안에 Skin 과 Skill 이 공존한다고 가정했습니다.
// 챔피온이란 추상 객체에 skin 뿐만아니라, skill, 특징 등등 계층구조에 추가될수록 무거워짐 (수많은 클래스가 추가됨)
// 따라서, 또 다른 차원의 액션들을 분리해서 다른 추상 객체로 만들어서 이런 문제에서 벗어나느것이 브릿지 패턴
public interface Champion extends Skin {

    void move();

    void skillQ();

    void skillW();

    void skillE();

    void skillR();

}

 

스킨을 적용한 챔피언

// 하나의 계층구조로 다양한 특징들을 표현하려다 보니,
// 계층구조가 커지고 각각의 자식클래스를 구현하는 과정이 다른 클래스와 비슷하고 중복코드 같아보임
public class 정복자아리 implements Champion {
    @Override
    public void move() {
        System.out.println( getName() + " 아리 move");
    }

    @Override
    public void skillQ() {
        System.out.println( getName() + " 아리 Q");
    }

    @Override
    public void skillW() {
        System.out.println( getName() + " 아리 W");
    }

    @Override
    public void skillE() {
        System.out.println( getName() + " 아리 E");
    }

    @Override
    public void skillR() { System.out.println( getName() + " 아리 R");}

    @Override
    public String getName() {
        return "정복자 Skin";
    }

}

뭔가 중복이 많아보임

 

이렇게 계층구조가 무거워짐에 따라 중복코드도 늘어나고, 모든 기능을 한 객체에서 관리하기 때문에 의존도도 높아집니다.

또한 각기 따른 세부적인 특징들이 달라질수록 그에 따른 구현체들도 기하 급수적으로 늘어나게 됩니다.

위처럼 skin 이 달라질 때마다 구현체가 생기고, 같은 Skin 에 추가적인 특징이 더해주면 또다시 구현체가 늘어나겠죠

 

 

✔️ 브릿지 패턴 장점 및 단점

장점

  • 추상적인 코드를 구체적인 코드 변경 없이도 독립적으로 확장할 수 있다.
  • 추상적인 코드과 구체적인 코드를 분리하여 수 있다.

단점

  • 계층 구조가 늘어나 복잡도가 증가할 수 있다.

 

 

 


브릿지 패턴의 중점은 다른 특징을 가지는 계층구조를 분리해서 결국은 책임소재를 분리하고

다른 디자인패턴처럼 확장에 유연한 구조를 설계한다는 점에 있는 것 같습니다.

 

 

 

끝!

 

 

 

 


*참고

반응형