로그인 대화상자를 만든다고 생각해보자. 이 로그인 대화상자를 만드는데, 아이디와 비밀번호를 단순하게 구현한다면 복잡하지 않을 것이다. 하지만 만약 아이디와 비밀번호가 다 있어야 로그인 버튼을 활성화하고, 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(조정자) 역할 : Colleague와 통신하고 조정하는 API를 정의. 예제에서는 Mediator 인터페이스이다.
ConcreteMediator(구체적인 조정자) 역할 : 실제로 구현된 조정자이며, 이를 통해 실제로 멤버들을 조정하게 된다. 예제에서는 LoginFrame이다.
Colleague(동료)의 역할 : Mediator와 통신하는 API를 정의. 예제에서는 Colleague이다.
ConcreteCOlleague(구체적인 동료)의 역할 : Colleague의 인터페이스를 구현하고 실제로 변화 시에 조정자에게 요청하게 된다.
표시 활성/비활성 로직이 분산되어있다고 생각했을때와 비교해서, 버그가 발생됐을때, 디버깅하기 쉽지 않을 것이다. 하지만 colleagueChanged로 한곳에 집중시키는 경우는 버그가 발생했을때 이 부분만 보면 된다.
만약에 통신 경로가 증가하는 경우를 보면, 예를 들어 ColleagueButton이 ColleagueTextField를 건들이고, ColleagueButton -> ColleagueCheckBox를, ColleagueCheckBox -> ColleagueButton를 건들인다고 생각해보면, 통신 경로가 너무 많아지기 때문에 엄청나게 프로그램이 복잡해진다.
이 패턴을 사용하는 경우는, ConcreteColleage 역은 재사용하기 쉬우나, ConcreteMediator의 경우는 재사용이 쉽지 않다. 애플리케이션에 대한 의존성이 높기 때문이다.
참조 :
Java 언어로 배우는 디자인 패턴 입문