브릿지 패턴

JVM을 생각해보면 이해가 쉽습니다. 파일을 이동하는 명령어는 Linux에서는 mv, Windows에서는 move로 다릅니다. 하지만 Java에서는 Files.move()라는 하나의 코드로 어떤 OS에서든 파일을 이동할 수 있습니다. Java 코드(상위 제어 레이어)는 "파일을 이동해"라고 명령만 하고 실제로 파일이 움직이는건 OS에서 진행됩니다.

브릿지 패턴은 이처럼 상위 수준의 제어 레이어를 정의하고, 실제 작업은 구현 레이어(플랫폼)에 위임하는 패턴입니다.

브릿지 패턴은 흔히 "추상화와 구현의 분리"라고 설명되지만, "제어 레이어와 구현 레이어의 분리"로 보는 게 더 이해가 쉬운 것 같습니다.

 

연습 문제 코드부터 보겠습니다. 아래 내용과 요구사항을 보고 직접 리팩토링 해보세요.

public class Tv {
    private boolean enabled;
    private int volume;
    private int channel;

    void enable() {
        enabled = true;
        System.out.println("tv 켜짐");
    }
    void disable() {
        enabled = false;
        System.out.println("tv 꺼짐");
    }
    boolean isEnabled() { return enabled; }
    int getVolume() { return volume; }
    void setVolume(int v) { volume = Math.max(0, Math.min(100, v)); }
    int getChannel() { return channel; }
    void setChannel(int ch) { channel = ch; }
}

public class Radio {
    private boolean enabled;
    private int volume;
    private int channel;

    void enable() {
        enabled = true;
        System.out.println("라디오 켜짐");
    }
    void disable() {
        enabled = false;
        System.out.println("라디오 꺼짐");
    }
    boolean isEnabled() { return enabled; }
    int getVolume() { return volume; }
    void setVolume(int v) { volume = Math.max(0, Math.min(100, v)); }
    int getChannel() { return channel; }
    void setChannel(int ch) { channel = ch; }
}

public class TvControl {
    private final Tv tv;

    public TvControl(Tv tv) { this.tv = tv; }

    public void togglePower() {
        if (tv.isEnabled()) tv.disable();
        else tv.enable();
    }
    public void volumeUp() { tv.setVolume(tv.getVolume() + 10); }
    public void volumeDown() { tv.setVolume(tv.getVolume() - 10); }
    public void channelUp() { tv.setChannel(tv.getChannel() + 1); }
    public void channelDown() { tv.setChannel(tv.getChannel() - 1); }
}

public class RadioControl {
    private final Radio radio;

    public RadioControl(Radio radio) { this.radio = radio; }

    public void togglePower() {
        if (radio.isEnabled()) radio.disable();
        else radio.enable();
    }
    public void volumeUp() { radio.setVolume(radio.getVolume() + 10); }
    public void volumeDown() { radio.setVolume(radio.getVolume() - 10); }
    public void channelUp() { radio.setChannel(radio.getChannel() + 1); }
    public void channelDown() { radio.setChannel(radio.getChannel() - 1); }
}

public class Client {
    public static void main(String[] args) {
        RadioControl radioControl = new RadioControl(new Radio());
        radioControl.togglePower();

        TvControl tvControl = new TvControl(new Tv());
        tvControl.togglePower();
    }
}

 

브릿지 패턴의 구조 및 요구사항

현재 코드의 문제점은 리모컨(Control)과 디바이스(Tv, Radio)가 강하게 결합되어 있다는 점입니다. 새로운 디바이스(스피커)가 추가되면 SpeakerControl을 만들어야 하고, 새로운 리모컨 타입(고급 리모컨)이 추가되면 AdvancedTvControl, AdvancedRadioControl을 모두 만들어야 합니다. 이렇게 되면 클래스 수가 리모컨 종류 × 디바이스 종류만큼 기하급수적으로 증가하게 됩니다.

브릿지 패턴의 핵심은 제어 레이어(상위 레이어)가 자체적으로 실제 작업을 수행하지 않고 구현 레이어에 위임한다는 것입니다. RemoteControl은 togglePower()가 호출되면 "전원을 토글해야겠다"고 결정만 하고, 실제로 전원을 켜고 끄는 작업은 Device(Tv, Radio)에게 맡깁니다.

브릿지 패턴은 이 두 차원(리모컨, 디바이스)을 분리하여 각각 독립적으로 확장할 수 있게 합니다.

  • Implementation 인터페이스 = Device 인터페이스 생성 (구현 레이어의 공통 인터페이스)
  • ConcreteImplementions 클래스 = Tv, Radio가 Device 인터페이스를 구현 (실제 작업 수행)
  • Abstraction 클래스 = RemoteControl 클래스 생성 (상위 수준 제어 레이어, Device에 위임)
  • Refined Abstraction 클래스 = RemoteControl을 상속받는 AdvancedRemoteControl

요구사항

  • Device 인터페이스는 모든 디바이스가 공통으로 가져야 할 동작을 정의한다.
  • RemoteControl(Abstraction)은 Device를 합성(Composition)으로 가지며, 구체적인 디바이스 구현에 의존하지 않는다.
  • RemoteControl을 상속받는 AdvancedRemoteControl을 만들어 뮤트(mute) 기능을 추가해보세요.
  • 클라이언트는 RemoteControl 타입으로 어떤 디바이스든 제어할 수 있어야 한다.

 

정답

  • Device 인터페이스로 디바이스들의 공통 동작을 추상화하여 리모컨이 구체 클래스가 아닌 인터페이스에 의존하게 만들었습니다.
  • TvControl, RadioControl로 분리되어 있던 중복 코드가 RemoteControl 하나로 통합되었습니다.
  • 새로운 디바이스(Speaker)를 추가할 때 Device 인터페이스만 구현하면 되고, 새로운 리모컨(AdvancedRemoteControl)을 추가할 때 RemoteControl만 상속받으면 됩니다.
  • 클래스 수가 리모컨 종류 + 디바이스 종류로 선형적으로 증가하게 됩니다.
 
public interface Device {
    void enable();
    void disable();
    boolean isEnabled();
    int getVolume();
    void setVolume(int v);
    int getChannel();
    void setChannel(int ch);
}

public class Tv implements Device {
    private boolean enabled;
    private int volume;
    private int channel;

    @Override
    public void enable() {
        enabled = true;
        System.out.println("tv 켜짐");
    }

    @Override
    public void disable() {
        enabled = false;
        System.out.println("tv 꺼짐");
    }

    @Override
    public boolean isEnabled() { return enabled; }
    @Override
    public int getVolume() { return volume; }
    @Override
    public void setVolume(int v) { volume = Math.max(0, Math.min(100, v)); }
    @Override
    public int getChannel() { return channel; }
    @Override
    public void setChannel(int ch) { channel = ch; }
}

public class Radio implements Device {
    private boolean enabled;
    private int volume;
    private int channel;

    @Override
    public void enable() {
        enabled = true;
        System.out.println("라디오 켜짐");
    }

    @Override
    public void disable() {
        enabled = false;
        System.out.println("라디오 꺼짐");
    }

    @Override
    public boolean isEnabled() { return enabled; }
    @Override
    public int getVolume() { return volume; }
    @Override
    public void setVolume(int v) { volume = Math.max(0, Math.min(100, v)); }
    @Override
    public int getChannel() { return channel; }
    @Override
    public void setChannel(int ch) { channel = ch; }
}

public class RemoteControl {
    protected Device device;

    public RemoteControl(Device device) { 
        this.device = device; 
    }

    public void togglePower() {
        if (device.isEnabled()) {
            device.disable();
        } else {
            device.enable();
        }
    }

    public void volumeUp() { device.setVolume(device.getVolume() + 10); }
    public void volumeDown() { device.setVolume(device.getVolume() - 10); }
    public void channelUp() { device.setChannel(device.getChannel() + 1); }
    public void channelDown() { device.setChannel(device.getChannel() - 1); }
}

public class AdvancedRemoteControl extends RemoteControl {

    public AdvancedRemoteControl(Device device) {
        super(device);
    }

    public void mute() {
        device.setVolume(0);
        System.out.println("음소거!");
    }
}

public class Client {
    public static void main(String[] args) {
        RemoteControl radioControl = new RemoteControl(new Radio());
        radioControl.togglePower();

        RemoteControl tvControl = new RemoteControl(new Tv());
        tvControl.togglePower();

        // 고급 리모컨으로 TV 제어
        AdvancedRemoteControl advancedTvControl = new AdvancedRemoteControl(new Tv());
        advancedTvControl.togglePower();
        advancedTvControl.mute();
    }
}

+ Recent posts