디자인패턴 (Mediator)

백종현·2023년 6월 9일
0

Mediator 패턴

문제


로그인 대화상자를 만든다고 생각해보자. 이 로그인 대화상자를 만드는데, 아이디와 비밀번호를 단순하게 구현한다면 복잡하지 않을 것이다. 하지만 만약 아이디와 비밀번호가 다 있어야 로그인 버튼을 활성화하고, Guest 로그인을 추가해서 게스트 로그인인 경우는 비밀번호를 삭제한다고 생각해보자. 이 경우에 확장성을 위해 로그인 버튼과 아이디 박스, 비밀번호 박스 등을 각각의 클래스로 나눈다고 생각해보자, 이는 굉장히 코딩하기 어려워 질 것이다. 만약 중재자가 없이 이러한 로그인 박스를 구현하면, 아래와 같이 굉장히 복잡한 의존관계를 가지게 될 것이다.

개념

객체 간의 협력을 중재자를 통해서만 수행하는 패턴이다. 즉, 멤버는 협력이 필요할 시 중재자에게 이를 알리고, 중재자만이 멤버에게 행동을 요구하는 패턴이다.

코드

이름설명
Mediator중재자의 API를 정하는 인터페이스
Colleague멤버의 API를 정하는 인터페이스
ColleagueButton멤버의 구현체, 버튼을 나타내는 클래스
ColleagueTextField멤버의 구현체, 텍스트박스를 나타내는 클래스
ColleagueCheckbox멤버의 구현체, 체크박스을 나타내는 클래스
LoginFrame중재자의 구현체, 로그인 대화상자를 나타내는 클래스
Main동작 테스트 용 클래스
public interface Mediator {
    // Colleague를 생성한다 
    public abstract void createColleagues();

    // Colleage의 상태가 변화했을 때 호출된다
    public abstract void colleagueChanged();
}

public interface Colleague {
    // Mediator를 설정한다 
    public abstract void setMediator(Mediator mediator);

    // Mediator에서 활성/비활성을 지시한다
    public abstract void setColleagueEnabled(boolean enabled);
}
public class ColleagueButton extends Button implements Colleague {
    private Mediator mediator;

    public ColleagueButton(String caption) {
        super(caption);
    }

    // Mediator를 설정한다 
    @Override
    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }

    // Mediator에서 활성/비활성을 지시한다
    @Override
    public void setColleagueEnabled(boolean enabled) {
        setEnabled(enabled);
    }
}
public class ColleagueCheckbox extends Checkbox implements ItemListener, Colleague {
    private Mediator mediator;

    public ColleagueCheckbox(String caption, CheckboxGroup group, boolean state) {
        super(caption, group, state);
    }

    // Mediator를 설정한다 
    @Override
    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }

    // Mediator에서 활성/비활성을 지시한다
    @Override
    public void setColleagueEnabled(boolean enabled) {
        setEnabled(enabled);
    }

    @Override
    public void itemStateChanged(ItemEvent e) {
        // 상태가 변화하면 Mediator에 알린다
        mediator.colleagueChanged();
    }
public class ColleagueTextField extends TextField implements TextListener, Colleague {
    private Mediator mediator;

    public ColleagueTextField(String text, int columns) {
        super(text, columns);
    }

    // Mediator를 설정한다 
    @Override
    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }

    // Mediator에서 활성/비활성을 지시한다
    @Override
    public void setColleagueEnabled(boolean enabled) {
        setEnabled(enabled);
        // 활성/비활성에 맞게 배경색을 변경한다
        setBackground(enabled ? Color.white : Color.lightGray);
    }

    @Override
    public void textValueChanged(TextEvent e) {
        // 문자열이 변화했으면 Mediator에 알린다
        mediator.colleagueChanged();
    }
}

위와 같이 mediator.colleagueChanged();를 통해 Colleage의 변화가 생길 시 mediator에게 이를 알린다.

Mediator 구현부이다.

public class LoginFrame extends Frame implements ActionListener, Mediator {
    private ColleagueCheckbox checkGuest;
    private ColleagueCheckbox checkLogin;
    private ColleagueTextField textUser;
    private ColleagueTextField textPass;
    private ColleagueButton buttonOk;
    private ColleagueButton buttonCancel;

    // Colleague를 생성하고 배치한 후에 표시한다
    public LoginFrame(String title) {
        super(title);

        // 배경색을 설정한다
        setBackground(Color.lightGray);

        // 레이아웃 매니저를 사용해 4×2 그리드를 만든다
        setLayout(new GridLayout(4, 2));

        // Colleague를 생성한다 
        createColleagues();

        // 배치한다 
        add(checkGuest);
        add(checkLogin);
        add(new Label("Username:"));
        add(textUser);
        add(new Label("Password:"));
        add(textPass);
        add(buttonOk);
        add(buttonCancel);

        // 활성/비활성 초기 설정을 한다
        colleagueChanged();

        // 표시한다 
        pack();
        setVisible(true);
    }

    // Colleague를 생성한다
    @Override
    public void createColleagues() {
        // CheckBox
        CheckboxGroup g = new CheckboxGroup();
        checkGuest = new ColleagueCheckbox("Guest", g, true);
        checkLogin = new ColleagueCheckbox("Login", g, false);

        // TextField
        textUser = new ColleagueTextField("", 10);
        textPass = new ColleagueTextField("", 10);
        textPass.setEchoChar('*');

        // Button
        buttonOk = new ColleagueButton("OK");
        buttonCancel = new ColleagueButton("Cancel");

        // Mediator를 설정한다 
        checkGuest.setMediator(this);
        checkLogin.setMediator(this);
        textUser.setMediator(this);
        textPass.setMediator(this);
        buttonOk.setMediator(this);
        buttonCancel.setMediator(this);

        // Listener 설정
        checkGuest.addItemListener(checkGuest);
        checkLogin.addItemListener(checkLogin);
        textUser.addTextListener(textUser);
        textPass.addTextListener(textPass);
        buttonOk.addActionListener(this);
        buttonCancel.addActionListener(this);
    }

    // Colleage의 상태가 바뀌면 호출된다
    @Override
    public void colleagueChanged() {
        if (checkGuest.getState()) {
            // 게스트 로그인 
            textUser.setColleagueEnabled(false);
            textPass.setColleagueEnabled(false);
            buttonOk.setColleagueEnabled(true);
        } else {
            // 사용자 로그인 
            textUser.setColleagueEnabled(true);
            userpassChanged();
        }
    }

    // textUser 또는 textPass의 변경이 있다 
    // 각 Colleage의 활성/비활성을 판정한다
    private void userpassChanged() {
        if (textUser.getText().length() > 0) {
            textPass.setColleagueEnabled(true);
            if (textPass.getText().length() > 0) {
                buttonOk.setColleagueEnabled(true);
            } else {
                buttonOk.setColleagueEnabled(false);
            }
        } else {
            textPass.setColleagueEnabled(false);
            buttonOk.setColleagueEnabled(false);
        }
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println(e.toString());
        System.exit(0);
    }
}

만약 변화의 요청이 들어온 경우, colleagueChanged() -> textUser.setColleagueEnabled(false)와 같은 방향으로 다시 행동을 요청하게 된다.

public class Main {
    static public void main(String[] args) {
        new LoginFrame("Mediator Sample");
    }
}

Mediator 패턴의 요소

Mediator(조정자) 역할 : Colleague와 통신하고 조정하는 API를 정의. 예제에서는 Mediator 인터페이스이다.
ConcreteMediator(구체적인 조정자) 역할 : 실제로 구현된 조정자이며, 이를 통해 실제로 멤버들을 조정하게 된다. 예제에서는 LoginFrame이다.
Colleague(동료)의 역할 : Mediator와 통신하는 API를 정의. 예제에서는 Colleague이다.
ConcreteCOlleague(구체적인 동료)의 역할 : Colleague의 인터페이스를 구현하고 실제로 변화 시에 조정자에게 요청하게 된다.

왜 Mediator를 사용할까?

표시 활성/비활성 로직이 분산되어있다고 생각했을때와 비교해서, 버그가 발생됐을때, 디버깅하기 쉽지 않을 것이다. 하지만 colleagueChanged로 한곳에 집중시키는 경우는 버그가 발생했을때 이 부분만 보면 된다.

만약에 통신 경로가 증가하는 경우를 보면, 예를 들어 ColleagueButton이 ColleagueTextField를 건들이고, ColleagueButton -> ColleagueCheckBox를, ColleagueCheckBox -> ColleagueButton를 건들인다고 생각해보면, 통신 경로가 너무 많아지기 때문에 엄청나게 프로그램이 복잡해진다.

주의점

이 패턴을 사용하는 경우는, ConcreteColleage 역은 재사용하기 쉬우나, ConcreteMediator의 경우는 재사용이 쉽지 않다. 애플리케이션에 대한 의존성이 높기 때문이다.

참조 :
Java 언어로 배우는 디자인 패턴 입문

profile
노력하는 사람

0개의 댓글