2015年6月25日木曜日

1-19. Observer パターン

1つのオブジェクトの状態が変化したとき、全ての依存するオブジェクトに通知され自動的に
更新されるように、オブジェクト間の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() に相当
	//	    }
	//	}
	//}
}

0 件のコメント:

コメントを投稿