CS/디자인패턴

[디자인패턴] Bridge 패턴(브릿지 패턴)

mabb 2023. 6. 12. 15:42
반응형

▶디자인 패턴 ( Design Pattern)

:프로그램 개발 시 문제 해결을 위하여 빈번히 사용되는 개발자들의 경험, 내적인 축적에 대하여,
GoF(Gang of Four) 라 불리는 4인의 개발자들이 각각을 패턴으로 정의하고 이름을 붙였다.
이를 디자인 패턴 (Design Pattern) 이라고 한다.
 23개의 디자인 패턴을
『Elements of Reusable Object-Oriented Software』
라는 책으로 발간하였다.

Gang of Four 분들은 모두 안경을 썼다.

 

▷디자인 패턴의 용어를 빌리면 서로의 아이디어를 보다 용이하게 비교, 논의할 수 있게 된다.
▷재사용과 기능확장이 쉬운 소프트웨어를 만들기 위한 유익한 기법이 바로 디자인 패턴이다.

 

---------------------------------------------------------
1. Iterator
2. Adapter
3. Template Method
4. Factory Method
5. Singleton
6. Prototype
7. Builder
8. Abstract Factory
9. Bridge
10. Strategy
11. Composite
12. Decorator
13. Visitor
14. Chain of Responsibility
15. Facade
16. Mediator
17. Observer
18. Memento
19. State
20. Flyweight
21. Proxy
22. Command
23. Interpreter
---------------------------------------------------------

▶Bridge 패턴

"기능과 구현의 분리"
"동물의 동작과 동물의 종류를 분리"


-왜 사용하는가
-클래스다이어그램
-각 클래스(인터페이스)의 역할
-Java소스

▶Bridge 패턴에 대한 이해

브릿지패턴 도식화
동물과 동물의 동작으로 비유하여 생각해봄

-클래스 계층에 있어서 기능과 구현을 분리하고, 기능부에서는 최상위 구현 타입의 필드를 생성자에서 주입받아(이 필드가 브리지 역할) 이 필드를 이용하여 기능을 수행하는 패턴이다.

-기능은 동작이고 구현은 기능을 수행하는 타입이라고 이해한다.
소리내기() 동작은 호랑이냐 사자냐 코끼리냐 개미냐에 따라 달라진다.
여기서 소리내기() 동작이 기능, 동물이 구현의 상위클래스(추상,인터페이스),  호랑이, 사자, 코끼리, 개미가 구현 클래스가 된다. 

-기능부와 구현부를 나누어서, 기능 추가와 구현 방식 추가라는 확장을 용이하게 한다.

-브리지 패턴을 이용하면 기능부와 구현부가 혼재된 혼란스러운 상황을 막을 수 있다.

-하위 클래스를 만들 때 상위 클래스의 기능을 '확장' 할 수도 있고 상위클래스의 인터페이스를 '구현' 할 수도 있다.

-하위클래스에서 기능과구현이 혼재되면 클래스 계층을 복잡하게 한다.

-클래스 계층이란 상위클래스(부모)와 하위클래스(자식)의 계층을 말한다.

-기능을 추가하는(확장하는) 클래스 계층을 '기능의 클래스 계층'이라 할 수 있다.

-상위 클래스(인터페이스)를 구체적으로 구현하는 클래스 계층을 '구현의 클래스 계층'이라 할 수 있다.

-기능의 클래스 계층에서는 구현의 클래스 계층에서 최상위 클래스 타입을 전달받아 사용한다.

-구현을 나타내는 클래스의 인스턴스를 필드에 저장하여 사용하는데, 이 필드가 기능의 클래스 계층과 구현의 클래스 계층의'다리(Bridge)'이다.

-기능의 클래스 계층과 구현의 클래스 계층, 즉 기능계층과 구현계층을 분리해두면 확장이 편리해진다.

-기능을 추가하기 위하여 구현 계층을 수정할 필요가 전혀 없다.

-기능 계층에 새롭게 추가한 기능은 '모든 구현'에서 이용할 수 있다.

-상속(Inheritance)은 강한 연결이고 위임(Delegation)은 약한 연결이다.

-상속은 SuperClass extends SubClass와 같이 '소스 코드' 로서 강하게 명시되어 있기 때문에 이 관계를 끊기 위해서는 소스 코드를 바꾸어 주어야 한다.

-위임은 주입받기에 따라 클래스 간의 관계를 척척 바꾸기 편리하다.

 

▶왜 사용하는가

보통 서브 클래스를 통하여 1) 기능을 확장하거나 2) 구현 방식을 추가하거나 할 수 있다. 구현과 기능을 모두 포함하는 클래스를 만든다면  확장이 불편하고 수정이 용이하지 않을 것이다. 브리지 패턴을 이용하면 새롭게 추가하는 기능을 모든 구현타입에서 사용할 수 있고, 새롭게 추가하는 구현으로 모든 기능을 사용할 수 있다.

ex) 기능과 구현이 혼재된 경우,  호랑이는 소리내기(),달리기() 기능을, 사자는  소리 내기(), 달리기() 기능을 각각 자기 자신의 클래스에서 정의하고 이를  수행한다. 여기서 각각에 먹기() 기능을 추가해 주려면 호랑이 타입과 사자 타입에 모두 먹기() 기능 추가를 위해 소스를 수정하여야 한다. 만약 이 상황에서 소리 내기(), 달리기(), 먹기() 기능을 수행하는 코끼리 타입을 추가하고 잠자기()를 또 추가한다면 또 호랑이와 사자 각각에 잠자기() 기능을 추가해야 한다. 여기서 동물과 동물이 할 수 있는 동작을 분리하는 것이 브리지 패턴이다. 코끼리 타입을 추가하면 코끼리 타입으로 기능부에 구현된 모든 기능을 이용할 수 있으며, 잠자기() 기능을 추가하면 호랑이, 사자, 코끼리는  소스 수정 없이  잠자기() 기능을 수행할 수 있게 된다.

 

▶클래스다이어그램

브릿지 패턴의 클래스다이어그램

 

▶각 클래스(인터페이스)의 역할

▷'기능의 클래스 계층'에서 최상위 클래스

-최상위 구현 타입을 필드로 가진다. (HAS - A)
-wrapper 패턴과 Template Method 패턴으로 기능을 구현한다.

▷기능을 확장하는 서브 클래스들

-상위 타입에서 Wrapping 한 메소드를 가지고 새로운 기능을 만든다.

 

▷'구현의 클래스 계층'에서 최상위 클래스

-기능에 필요한 메소드의 인터페이스(뼈대)가 되는 추상메소드를 정의한다.

 

▷다양하게 구현하는 서브 클래스들

-기능을 수행하는 새로운 타입이 되는 클래스이다.

 

▶Java 소스

*java1.8 이전 버전을 사용하는 교재를 참고하였습니다.
Java API 문서 등을 참조하여 제네릭을 적용하는 등 소스를 개선할 필요가 있습니다.
(https://docs.oracle.com/en/java/javase/20/docs/api/)

준비하고 처리하고 완료하는 일련의 작업을 수행하는 기능 클래스가 있다. 일련의 작업에 대한 작업 결과를 반말로 알려주는 '반말 모드'와 존대로 알려주는 '존대 모드'가 있다. 준비 1회, 처리 1회, 완료 1회만 처리하는 작업 말고 처리를 여러 번 수행하거나 준비를 하지 않는 작업을 추가하여야 하는 상황이 생겼다고 가정한 예제이다. 브리지 패턴으로 구조화하면 최소한의 수정으로 기능의 확장, 구현의 확장을 용이하게 할 수 있다.

▷FunctionClass.java

package main.java.designpattern.bridge;

public class FunctionClass {
    private ImplClass implClass; // 이 필드가 <기능의 클래스 계층>과 <구현의 클래스계층> 을 연결하는 Bridge..!

    public FunctionClass(ImplClass implClass){
        this.implClass = implClass;
    }
    public void func1(){
        System.out.println("====기능 수행====");
        wrapPrepare();
        wrapProcess();
        wrapComplete();
    }
    protected void wrapPrepare(){
        implClass.prepare();
    }
    protected void wrapProcess(){
        implClass.process();
    }
    protected void wrapComplete(){
        implClass.complete();
    }
}

 

▷FunctionAddFunc1.java

package main.java.designpattern.bridge;

public class FunctionAddFunc1 extends FunctionClass{
    public FunctionAddFunc1(ImplClass implClass) {
        super(implClass);
    }
    public void funcRepeat(int num){
        System.out.println("====반복 기능 수행====");
        wrapPrepare();
        for(int i=0; i<num; i++){
            wrapProcess();
        }
        wrapComplete();
    }
}

▷FunctionAddFunc2.java

package main.java.designpattern.bridge;

public class FunctionAddFunc2 extends FunctionClass{
    public FunctionAddFunc2(ImplClass implClass) {
        super(implClass);
    }
    public void funcNoPrepare(){
        System.out.println("====준비 없이 기능 수행====");
        wrapProcess();
        wrapComplete();
    }
}

▷ImplClass.java

package main.java.designpattern.bridge;

public interface ImplClass {
    public abstract void prepare();
    public abstract void process();
    public abstract void complete();
}

▷ConcreteImplClass1.java

package main.java.designpattern.bridge;

public class ConcreteImplClass1 implements ImplClass{
    @Override
    public void prepare() {
        System.out.println("\n 반말모드 시작");
        System.out.println("준비했다.");
    }
    @Override
    public void process() {
        System.out.println("처리한다.");
    }
    @Override
    public void complete() {
        System.out.println("완료했다.");
        System.out.println("반말모드 완료\n");
    }
}

▷ConcreteImplClass2.java

package main.java.designpattern.bridge;

public class ConcreteImplClass2 implements ImplClass{
    @Override
    public void prepare() {
        System.out.println("\n 존대모드 시작");
        System.out.println("준비했습니다.!");
    }
    @Override
    public void process() {
        System.out.println("처리합니다!.");
    }
    @Override
    public void complete() {
        System.out.println("완료했습니다!.");
        System.out.println("존대모드 완료\n");
    }
}

▷Main.java

package main.java.designpattern.bridge;

import java.util.*;

public class Main {
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        FunctionClass fc;
        FunctionAddFunc1 fc2;
        FunctionAddFunc2 fc3;
        ImplClass ic;

        System.out.println("모드를 입력하세요.\n" +
                           "반말모드: main.java.designpattern.bridge.ConcreteImplClass1\n" +
                           "존대모드: main.java.designpattern.bridge.ConcreteImplClass2\n");

        String input = sc.nextLine();
        try {
            ic = (ImplClass) Class.forName(input).newInstance();
            fc= new FunctionClass(ic);
            fc2= new FunctionAddFunc1(ic);
            fc3= new FunctionAddFunc2(ic);

            //기능 사용부///////////////////////////////////
            fc.func1();
            fc2.funcRepeat(3);
            fc3.funcNoPrepare();

        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }





    }
}

 

 

 

---------------------------------------------------------
※참고자료
-Java언어로 배우는 디자인 패턴 입문
-Headfirst Design Pattern
-나무위키, 위키백과, 네이버백과
-리팩토링구루(https://refactoring.guru/ko)

반응형