본문 바로가기
컴퓨터 과학/디자인패턴

[디자인패턴] 구조 패턴(7) : 브릿지(Bridge)

by webcodur 2024. 3. 25.
728x90

목차

     

    브릿지(Bridge)

    브릿지 디자인 패턴은 구조 패턴의 하나로, 추상화(abstraction)와 구현(implementation)을 분리해서 둘 사이의 결합도를 낮추는 데 목적을 둔다. 이렇게 하면 두 구성 요소를 독립적으로 확장할 수 있게 되어, 코드의 유연성과 재사용성이 증가한다.

    패턴명인 "브리지"는 물리적인 다리가 두 지점을 연결하는 것처럼, 이 패턴이 추상화와 구현 사이를 연결한다는 의미에서 유래되었다.

     

    패턴 미적용 예시

    게임 개발 시나리오에서, 다양한 유형의 캐릭터가 있고 각각 다른 무기를 사용한다고 가정해보자. 다음은 패턴 없이 캐릭터와 무기 간의 관계가 직접적으로 구현된 모습이다.

    // 캐릭터 클래스
    class Character
    {
        public string Name { get; set; }
    
        // 캐릭터가 사용하는 무기 유형을 직접 구현
        public void Attack()
        {
            Console.WriteLine($"{Name}가 검으로 공격한다");
        }
    }
    
    // 메인 메소드
    class Program
    {
        static void Main(string[] args)
        {
            Character knight = new Character { Name = "기사" };
            knight.Attack();
        }
    }
    

    이 코드는 캐릭터가 공격할 때 사용하는 무기 유형을 직접적으로 Character 클래스에 구현하고 있다. 새로운 무기 유형을 추가하거나, 다른 캐릭터 유형을 추가할 때마다 Character 클래스를 수정해야 하므로 확장성이 떨어진다.

     

    패턴 적용 예시

    이제 브리지 패턴을 적용해 예시를 작성해 보자. 우선 브리지 패턴의 기본 구성 요소는 다음과 같다. 이를 적용하면, 캐릭터와 무기 간의 관계를 유연하게 관리할 수 있다.

    • Implementor: 구현 클래스들의 인터페이스를 정의한다. 이는 실제 기능의 구현을 담당한다.
    • ConcreteImplementor: Implementor 인터페이스를 구현하는 실제 클래스들로, 구체적인 로직을 포함한다.
    • Abstraction: 고수준의 제어 로직을 제공하며, 구현 부분에 대한 참조를 유지한다.
    • RefinedAbstraction: Abstraction 클래스를 확장한 클래스들로, 더 구체적인 로직을 제공한다.
    // Implementor
    interface IWeapon
    {
        void Attack();
    }
    
    // ConcreteImplementorA
    class Sword : IWeapon
    {
        public void Attack()
        {
            Console.WriteLine("검으로 공격한다");
        }
    }
    
    // ConcreteImplementorB
    class Bow : IWeapon
    {
        public void Attack()
        {
            Console.WriteLine("활로 공격한다");
        }
    }
    
    // Abstraction
    class Character
    {
        protected IWeapon weapon;
    
        public Character(IWeapon weapon)
        {
            this.weapon = weapon;
        }
    
        public void Attack()
        {
            weapon.Attack();
        }
    }
    
    // RefinedAbstraction
    class Knight : Character
    {
        public Knight(IWeapon weapon) : base(weapon) {}
    }
    
    class Archer : Character
    {
        public Archer(IWeapon weapon) : base(weapon) {}
    }
    
    // 메인 메소드
    class Program
    {
        static void Main(string[] args)
        {
            Character knight = new Knight(new Sword());
            knight.Attack();
    
            Character archer = new Archer(new Bow());
            archer.Attack();
        }
    }
    

     

    이 예시에서는 Character 클래스(추상화)와 IWeapon 인터페이스(구현) 사이의 연결을 브리지 패턴을 이용하여 유연하게 만들었다. Character 클래스는 더 이상 특정 무기에 대한 정보를 직접적으로 가지고 있지 않으며, 대신 IWeapon 인터페이스를 통해 어떤 무기든지 사용할 수 있는 능력을 갖게 된다. 이로 인해 새로운 무기 유형이 추가되거나 새로운 캐릭터 유형이 도입될 때, 기존 코드를 변경하지 않고도 쉽게 확장할 수 있다는 장점이 있다.

     

    다음은 위 내용을 반영한 간단한 도식이다. 도식에서 볼 수 있듯이, 브리지 패턴은 캐릭터(추상화)와 무기(구현) 사이의 강한 결합을 제거하고, 이 둘 사이의 관계를 유연하게 만들어 준다. 이를 통해 새로운 캐릭터나 무기 유형을 추가하거나 변경할 때 기존 코드를 수정하지 않고도 확장할 수 있는 구조를 만들어 낸다.

                             [ Character ]
                                  |
                  -----------------------------------
                  |                                 |
              [ Knight ]                       [ Archer ]
                  |                                 |
                  |                                 |
              [ IWeapon ]                     [ IWeapon ]
                  ^                                 ^
                  |                                 |
        ---------------------           ---------------------
        |                   |           |                   |
    [ Sword ]           [ Bow ]     [ Sword ]           [ Bow ]
    
    

     

     

    예시 속 브릿지 구조

    • Character (추상화): 모든 캐릭터의 공통 기능을 정의한다. 이는 무기를 사용하는 기능(Attack())을 포함하며, IWeapon 인터페이스 타입의 멤버 변수를 통해 다양한 무기 구현체와 연결된다.
    • Knight, Archer (세분화된 추상화): 구체적인 캐릭터 유형을 나타낸다. 각각의 클래스는 Character로부터 상속받아 특정한 행동을 구현한다.
    • IWeapon (구현자 인터페이스): 무기에 대한 인터페이스를 정의하며, 모든 구체적인 무기 클래스들은 이 인터페이스를 구현한다(Attack() 메서드).
    • Sword, Bow (구체적인 구현체): IWeapon 인터페이스의 구현체로, 실제 무기의 동작을 정의한다. 예를 들어, Sword 는 검으로 공격하는 방식을, Bow는 활로 공격하는 방식을 구현한다.

     

    결론

    브리지 패턴을 적용함으로써 얻을 수 있는 주요 이점을 정리하면 다음과 같다

    • 확장성: 추상화와 구현을 분리함으로써, 각각 독립적으로 변화하고 확장할 수 있다. 이는 시스템의 유연성을 증가시킨다.
    • 재사용성: 구현에 해당하는 부분을 재사용하기 용이하다. 다양한 추상화에서 같은 구현을 재사용할 수 있으며, 이는 코드 중복을 줄인다.
    • 결합도 감소: 브리지 패턴은 추상화와 구현 사이의 결합도를 낮춤으로써, 각 부분을 독립적으로 개발하고 유지보수할 수 있게 한다.

    패턴 미적용 예시에서 볼 수 있듯, 캐릭터와 무기 간의 직접적인 관계 설정은 코드의 확장성과 유연성을 제한한다. 새로운 무기 유형을 추가하거나 캐릭터 유형을 변경하려면 기존 클래스를 수정해야 하며, 이는 오픈/클로즈 원칙에도 위배된다(소프트웨어 개체는 확장에 대해서는 열려 있어야 하지만, 변경에 대해서는 닫혀 있어야 한다는 원칙).

     

    브리지 패턴을 적용한 예시에서는 이러한 문제를 해결한다. IWeapon 인터페이스를 도입함으로써 다양한 무기 구현을 추상화와 독립적으로 관리할 수 있게 되었고, 캐릭터 클래스는 이 인터페이스를 통해 무기를 사용한다. 이 방식은 새로운 무기나 캐릭터 유형을 시스템에 추가할 때 기존 코드의 변경 없이 확장할 수 있는 유연성을 제공한다.