更新されるように、オブジェクト間の1対多数の依存関係を定義します。
ある Subject の状態が変化したときに別の Subject の状態を変更したい場合、
一度 Observer に通知し、Observer が関係する Subject の変更を行います。
void client() throws InterruptedException {
ConcreteSubject subject = new ConcreteSubject();
Observer observer = new ConcreteObserver();
subject.addObserver(observer);
// スレッド内で実行
Thread subjectThread = new Thread(subject);
subjectThread.start();
subjectThread.join();
}
interface Subject {
public void addObserver(Observer observer);
public void _notify();
}
interface Observer {
public void update();
}
class ConcreteSubject implements Subject, Runnable {
private List<Observer> observers = new CopyOnWriteArrayList<Observer>();
public void addObserver(Observer observer) {
this.observers.add(observer);
}
public void _notify() {
for (Observer observer: this.observers) {
observer.update();
}
}
public void run() {
this._notify();
}
}
class ConcreteObserver implements Observer {
public void update() {
System.out.println("update");
}
}
実行結果:update
サンプルプログラムは、ダイアログの例です。ここではダイアログが Observer、ボタン・テキストボックス・リストといった部品が Subject に相当
します。
テキストを入力して Add ボタンを押すとリストに追加されますが、Subject である部品がほかの
部品を直接操作することはせず、一度 Observer であるダイアログを介して操作を行います。
Add ボタンを押す -> Button.processActionEvent(Subject.needsAdvice() に相当)
-> Dialog_ConcreteObserver.actionPerformed()(ConcreteObserver.consultation() に相当)
-> テキストボックスのテキストを取得し、リストに追加(ConcreteSubject.advice() に相当)
ボタン->ダイアログへの呼び出しは、標準ライブラリの Button クラスが行っています。
Button.addActionListener() によって ActionListener インターフェースを実装したダイアログを
登録することで、ボタンでアクションイベントが発生した際に
Dialog_ConcreteObserver.actionPerformed() が呼び出されます。
また、addWindowsListener ... の部分も Observer パターンで、WindowAdapter が Observer、
ダイアログが Subject に相当します。
実行結果:
dialog.actionPerformed(): from button0
dialog.actionPerformed(): from button0
dialog.actionPerformed(): from list0
dialog.actionPerformed(): from button1
package design_pattern;
import java.awt.Button;
import java.awt.Component;
import java.awt.Frame;
import java.awt.List;
import java.awt.TextField;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
/**
* Observer
* GoF, Code Complete
*
* 1つのオブジェクトの状態が変化したとき、全ての依存するオブジェクトに通知され自動的に
* 更新されるように、オブジェクト間の1対多数の依存関係を定義します。
*
* @author 2015/06/21 matsushima
*/
public class ObserverSample {
public static void main(String[] args) throws InterruptedException {
// ConcreteObserver: dialog
Dialog_ConcreteObserver dialog = new ObserverSample().new Dialog_ConcreteObserver();
dialog.setVisible(true);
}
/** ConcreteObserver: ダイアログ。 */
public class Dialog_ConcreteObserver extends Frame implements ActionListener {
private static final long serialVersionUID = 1L;
private TextField text;
private List list;
private Button buttonAdd, buttonClose;
public Dialog_ConcreteObserver() {
super("ObserverSample");
setLayout(null);
addWindowListener(new WindowAdapter() { // Subject.addObserver に相当。
public void windowClosing(WindowEvent e) { // ConcreteObserver.update() に相当。
System.exit(0);
}
});
this.setSize(290, 230);
// ConcreteSubject: text
text = new TextField();
text.setBounds(20, 50, 200, 25);
text.addActionListener(this); // Subject.addObserver に相当。
this.add(text);
// ConcreteSubject: buttonAdd
buttonAdd = new Button("Add");
buttonAdd.setBounds(220, 50, 50, 25);
buttonAdd.addActionListener(this); // Subject.addObserver に相当。
this.add(buttonAdd);
// ConcreteSubject: list
list = new List(5);
list.setBounds(20, 80, 250, 100);
list.addActionListener(this); // Subject.addObserver に相当。
this.add(list);
// ConcreteSubject: buttonClose
buttonClose = new Button("Close");
buttonClose.setBounds(220, 185, 50, 25);
buttonClose.addActionListener(this); // Subject.addObserver に相当。
this.add(buttonClose);
}
/** ConcreteObserver.update() に相当。 */
public void actionPerformed(ActionEvent e) {
//System.out.println(e);
System.out.println("dialog.actionPerformed(): from " + ((Component)e.getSource()).getName());
if (this.buttonAdd == e.getSource() // buttonAdd click
|| this.text == e.getSource()) { // text enter
if (!this.text.getText().isEmpty()) {
this.list.add(this.text.getText());
}
} else if (this.list == e.getSource()) { // list double-click
this.text.setText(this.list.getSelectedItem());
} else if (this.buttonClose == e.getSource()) { // buttonClose click
this.dispose();
}
}
}
///** Observer: ConcreteSubject からのイベント受け取り。 */
//public interface ActionListener extends EventListener {
// /** Observer.update() に相当。 */
// public void actionPerformed(ActionEvent e);
//}
///** ConcreteSubject: ボタン。 */
//public class Button(or TextField, List) extends Component {
// /** ConcreteSubject.needsAdvice() に相当。 */
// protected void processActionEvent(ActionEvent e) {
// ActionListener listener = actionListener;
// if (listener != null) {
// listener.actionPerformed(e); // ConcreteObserver.update() に相当
// }
// }
//}
}