AspectJ を使ったデザインパターンの改善と支援 >
Observer

asato <asato@ncfreak.com>

最終更新日 : 2004/6/5 (2002/9/26 より)

概要

ここでは、AspectJ を使っての、様々な Observer パターン [1] の実装を紹介します。

まず最初に、オブジェクト指向 Oserver パターンの実装における欠点について述べます。その後、様々な動機に基づいて、AspectJ による、いくつかの Observer パターンの実装を紹介します。

Observer

あるオブジェクトが状態を変えたとき、それに依存するすべてのオブジェクトに自動的にそのことを知らされ、また、それらが更新されるように、オブジェクト間に一対多の依存関係を定義する。

Observer パターンのバリエーション

実践的には、作ろうとしているアプリケーションのクラス構造が、厳密な Observer パターンにマッチしているかどうかは、重要ではありません。その時点での、様々な要因を考慮した上でのクラス構造の方が、プログラマにとっての関心事であるためです。

しかしながら、ここでは、主に GoF で紹介されている Observer パターンを AspectJ を使って実装することに焦点を当てています。そのため、何を Observer パターンと見なすのかどうか、や、どうなっていれば Observer パターンなのか、あるいは、どこまでが Observer パターンなのか、かについて触れておく必要があると思います。

Observer パターンの大きなバリエーションの 1 つとしては、push モデルの Observer パターンなのか、pull モデルの Observer パターンなのか、というものがあります [1, p.318] [2, p.315]:

Observer パターンであることの要求

この節では、Observer パターンであることのいくつかの要求について述べます。あるいは、(特に、AspectJ を使って、アスペクト指向的な) Observer パターンを実装しようとしているときに考慮しなければいけない点を考えます。 これらの点を考慮することは重要です。というのも、一対一の関係のみを満足する Observer パターンの実装と、一対複数の関係をサポートする Observer パターンの実装では、その実装方法に違いがでるかもしれないためです。

AspectJ を使った Observer に関するテクニックの種類

AspectJ を使った Observer の実装

結果: サンプルコード:
package dp.observer;

public interface Subject {}
import java.util.List;
import java.util.Vector;
import java.util.Iterator;

public aspect SubjectAspect {

	private List Subject.observers = new Vector();

	public void Subject.addObserver(Observer observer) {
		observers.add(o);
	}
	public void Subject.notifyObservers() {
		for(Iterator itr = observers.iterator() ; itr.hasNext(); ) {
			( (Observer)itr.next() ).update();
		}
	}

	public void Observer.update() {}
}
package dp.observer;

public class ConcreteSubject {

	private String state = "xxx";

	public void setState(String newState) {
		this.state = newState;
	}
	public String getState() {
		return state;
	}

	private static aspect InnerSubjectAspect {

		declare parents : ConcreteSubject implements Subject;

		pointcut setState(Subject subject) :
			this(subject) &&
			execution( void ConcreteSubject.setState(String) );

		after(Subject subject) : setState(subject) {
			subject.notifyObservers();
		}
	}
}
package dp.observer;

public class  MyObserver implements Observer {
	public void update(ConcreteSubject subject) {
		System.out.println("update: " + subject.getState() );
	}

	private static aspect InnerAspect {

		pointcut update(ConcreteSubject subject, MyObserver observer) :
			this(subject) && 
			target(observer) &&
			call(void Observer.update() );

		void around(ConcreteSubject subject, MyObserver observer) : update(subject, observer)
		{
			observer.update(subject);
		}
	}
}
package dp;

import dp.observer.*;

public class Client {

	public static void main(String[] args) {

		Observer observer = new MyObserver();

		ConcreteSubject subject = new ConcreteSubject();

		subject.addObserver(observer);

		subject.setState("yyy");
	}
}
update: yyy

実装 - その 2

public class Point {

	private int x;
	private int y;

	public Point(int x, int y) {
		this.x = x;
		this.y = y;
	}

	public void setX(int x) {
		this.x = x;
	}
	public void setY(int x) {
		this.y = y;
	}
}
public class CoordinateObserver {
	public void update(Point point) {
		System.out.println("ok");
	}
}
public aspect PointAspect pertarget( subject() ) {

	pointcut subject() : target(Point);

	private List observers = new ArrayList();

	public void Point.addObserver(CoordinateObserver observer) {
		PointAspect.aspectOf(this).addObserver(observer);
	}

	private void addObserver(CoordinateObserver observer) {
		observers.add(observer);
	}

	pointcut subjectChange(Point point) :
		target(point) &&
		( call( void Point.setX(int) ) || call( void Point.setY(int) ) );


	after(Point point) returning : subjectChange(point) {
		for(Iterator itr = observers.iterator() ; itr.hasNext(); ) {
			( (CoordinateObserver)itr.next() ).update(point);
		}
	}
}
public class Client {

	public static void main(String[] args) {

		Point p = new Point(1, 1);
		p.addObserver( new CoordinateObserver() );

		p.setX(5); // ok
	}
}

実装 - その 3

段階 - その 1

import java.awt.*;

public class Point {

	private int x;
	private int y;
	private Color color;

	public Point(int x, int y) {
		this.x = x;
		this.y = y;
	}

	public void setX(int x) {
		this.x = x;
	}

	public void setY(int x) {
		this.y = y;
	}
	
	public void setColor(Color color) {
		this.color = color;
	}
}
public aspect PointAspect {

	protected interface Observer {
		public void update(Point point);
	}

	declare parents: CoordinateObserver implements Observer;
	declare parents: ColorObserver      implements Observer;

	public void Point.addObserver(Observer observer) { }

	public void Point.notifyObservers(List observers) {
		for(Iterator itr = observers.iterator() ; itr.hasNext(); ) {
			( (Observer)itr.next() ).update(this);
		}
	}
}
public class CoordinateObserver {

	public void update(Point point) {
		System.out.println("CoordinateObserver");
	}

	private static aspect Observer {

		private List observers = new ArrayList();

		pointcut subjectChange(Point point) :
			target(point) &&
			( call( void Point.setX(int) ) || call( void Point.setY(int) ) );

		after(Point point) : subjectChange(point) {
			point.notifyObservers(observers);
		}

		void around(CoordinateObserver observer) :
			args(observer) &&
			call( void Point.addObserver(PointAspect.Observer) )
		{
			observers.add(observer);
		}
	}
}
import java.awt.Color;
import java.util.*;

public class ColorObserver {

	public void update(Point point) {
		System.out.println("ColorObserver");
	}

	private static aspect Observer {

		private List observers = new ArrayList();

		pointcut subjectChange(Point point) :
			target(point) &&
			call( void Point.setColor(Color) );

		after(Point point) : subjectChange(point) {
			point.notifyObservers(observers);
		}

		void around(ColorObserver observer) :
			args(observer) &&
			call( void Point.addObserver(PointAspect.Observer) )
		{
			observers.add(observer);
		}
	}
}
public class Client {

	public static void main(String[] args) {

		Point p = new Point(1, 1);

		p.addObserver( new CoordinateObserver() );
		p.addObserver( new CoordinateObserver() );
		p.addObserver( new ColorObserver() );
		p.addObserver( new ColorObserver() );

		p.setX(5);
		p.setColor(Color.RED);
	}
}

段階 - その 2

public abstract aspect PointObserver {

	protected List observers = new ArrayList();

	abstract pointcut subjectChange();

	after(Point point) : target(point) && subjectChange() {
		point.notifyObservers(observers);
	}
}
public class CoordinateObserver {

	public void update(Point point) {
		System.out.println("CoordinateObserver");
	}

	private static aspect Observer extends PointObserver {

		pointcut subjectChange() :
			call( void Point.setX(int) ) || call( void Point.setY(int) );

		void around(CoordinateObserver observer) :
			args(observer) &&
			call( void Point.addObserver(PointAspect.Observer) )
		{
			observers.add(observer);
		}
	}
}
public class ColorObserver {

	public void update(Point point) {
		System.out.println("ColorObserver");
	}

	private static aspect Observer extends PointObserver {

		pointcut subjectChange() : call( void Point.setColor(Color) );

		void around(ColorObserver observer) :
			args(observer) &&
			call( void Point.addObserver(PointAspect.Observer) )
		{
			observers.add(observer);
		}
	}
}

段階 - その 3

public abstract aspect PointObserver {

	protected List observers = new ArrayList();

	abstract pointcut subjectChange();

	after(Point point) : target(point) && subjectChange() {
		point.notifyObservers(observers);
	}


	abstract pointcut observerType();

	pointcut addObserver() :
		call( void Point.addObserver(PointAspect.Observer) );

	void around() : observerType() && addObserver() {
		observers.add( thisJoinPoint.getArgs()[0] );
	}
}
public class CoordinateObserver {

	public void update(Point point) {
		System.out.println("CoordinateObserver");
	}

	private static aspect Observer extends PointObserver {

		pointcut subjectChange() :
			call( void Point.setX(int) ) || call( void Point.setY(int) );

		pointcut observerType() : args(CoordinateObserver);
	}
}
public class ColorObserver {

	public void update(Point point) {
		System.out.println("ColorObserver");
	}

	private static aspect Observer extends PointObserver {

		pointcut subjectChange() : call( void Point.setColor(Color) );

		pointcut observerType() : args(ColorObserver);
	}
}

段階 - その 4

public abstract aspect PointObserver pertarget( subject() ) {

	pointcut subject() : target(Point);

	protected List observers = new ArrayList();


	abstract pointcut subjectChange();

	after(Point point) : target(point) && subjectChange() {
		point.notifyObservers(observers);
	}


	abstract pointcut observerType();

	pointcut addObserver() :
		call( void Point.addObserver(PointAspect.Observer) );

	void around() : observerType() && addObserver() {
		observers.add( thisJoinPoint.getArgs()[0] );
	}
}

段階 - その 5

public abstract aspect PointObserver pertarget( subject() ) {

	pointcut subject() : target(Point);

	private List observers = new ArrayList();


	abstract pointcut subjectChange();

	after(Point point) : target(point) && subjectChange() {
		point.notifyObservers(observers);
	}


	abstract pointcut observerType();

	pointcut addObserver() :
		call( void Point.addObserver(PointAspect.Observer) );

	void around(PointAspect.Observer observer) :
		args(observer) && observerType() && addObserver()
	{
		observers.add( observer );
	}
}

実装 - その 4

public class Point {

	private int x;
	private int y;
	private Color color;

	public Point(int x, int y) {
		this.x = x;
		this.y = y;
	}

	public void setX(int x) {
		this.x = x;
	}

	public void setY(int x) {
		this.y = y;
	}
}
public aspect CoordinateObserver pertarget( subject() ) {

	private pointcut subject() : target(Point);

	private List Point.observers = new ArrayList();

	public static void observe(Point p) {
		p.addObserver( new Observer() );
	}

	private void Point.addObserver(Observer observer) {
		observers.add(observer);
	}

	private void Point.notifyObservers() {
		for(Iterator itr = observers.iterator() ; itr.hasNext(); ) {
			( (Observer)itr.next() ).update(this);
		}
	}

	pointcut subjectChange(Point point) :
		target(point) &&
		( call( void Point.setX(int) ) || call( void Point.setY(int) ) );

	after(Point point) : subjectChange(point) {
		point.notifyObservers();
	}

	protected static class Observer {

		public void update(Point point) {
			System.out.println("CoordinateObserver - " + this + " - " + point);
		}
	}
}

実装 - その 5

この実装では、AspectJ をうまく使うことにより、元々 Observer パターンが使われていないとして設計済みの他人のクラスに対して、Observer パターンを導入することを目的としたコードを紹介します。

段階 - その 1

public class Point {

	private int x;
	private int y;

	public Point(int x, int y) {
		this.x = x;
		this.y = y;
	}

	public void setLocation(int x, int y) {
		this.x = x;
		this.y = y;
	}
}
public interface Observer {
	public void update();
}
public class Screen implements Observer {
	public void update() {
		System.out.println("Screen");
	}
}
public aspect PointAspect {

	private List Point.observers = new ArrayList();

	public void Point.addObserver(Observer observer) {
		observers.add(observer);
	}
	
	private void Point.notifyObservers() {
		for(Iterator itr = observers.iterator() ; itr.hasNext(); ) {
			( (Observer)itr.next() ).update();
		}
	}

	private pointcut subjectChange(Point point) :
		target (point) && call( void Point.setLocation(int, int) );

	after(Point point) returning : subjectChange(point) {
		point.notifyObservers();
	}
}
public class Client {

	public static void main(String[] args) {

		Point p = new Point(1, 1);
		p.addObserver( new Screen() );

		p.setLocation(5, 5);
	}
}
Screen

選択肢

段階 - その 2

public abstract aspect ObserverProtocol {

	protected interface Subject { }

	protected interface Observer {
		public void update();
	}

	private List Subject.observers = new ArrayList();

	public void Subject.addObserver(Observer observer) {
		observers.add(observer);
	}

	public void Subject.notifyObservers() {
		for(Iterator itr = observers.iterator() ; itr.hasNext(); ) {
			( (Observer)itr.next() ).update();
		}
	}

	protected abstract pointcut subjectChange(Subject subject);

	after(Subject subject) returning : subjectChange(subject) {
		subject.notifyObservers();
	}
}
public aspect PointAspect extends ObserverProtocol {

	declare parents : Point  implements Subject;
	declare parents : Screen implements Observer;


	protected pointcut subjectChange(Subject subject) :
		target(subject) && call( void Point.setLocation(int, int) );
}

段階 - その 3

もし、update メソッドの引数として何か渡したいとしたら?
public aspect PointAspect extends ObserverProtocol {

	declare parents : Point  implements Subject;
	declare parents : Screen implements Observer;

	private void Screen.update(Point point) {
		System.out.println("Screen: " + point);
	}

	private pointcut update(Point point, Screen screen) :
		this(point) && target(screen) && call(void Observer.update());

	void around(Point point, Screen screen) : update(point, screen) {
		screen.update(point);
	}

	protected pointcut subjectChange(Subject subject) :
		target(subject) && call( void Point.setLocation(int, int) );
}
public class Client {

	public static void main(String[] args) {

		Point p = new Point(1, 1);
		p.addObserver( new Screen() );

		p.setLocation(5, 5);
	}
}
Screen: dp.observer.Point@187aeca

選択肢

段階 - その 4 : リファクタリング

public abstract aspect ObserverProtocol {

	// ... 省略

	protected pointcut update() : call( void Observer.update() );

	// ... 省略
}
public aspect PointAspect extends ObserverProtocol {

	// ... 省略

	private pointcut updateCall(Point point, Screen screen) :
		this(point) && target(screen) && update();

	void around(Point point, Screen screen) : updateCall(point, screen) {
		screen.update(point);
	}

	// ... 省略
}

段階 - その 5 : リファクタリング

public abstract aspect ObserverProtocol {

	// ... 前と同じ

	protected abstract pointcut subjectChange();

	after(Subject subject) returning : subjectChange() && target(subject) {
		subject.notifyObservers();
	}
}

public aspect PointAspect extends ObserverProtocol {

	// ... 前と同じ

	protected pointcut subjectChange() :
		call( void Point.setLocation(int, int) );
}

段階 - その 6 : その他のメソッド

public class Point {

	private int x;
	private int y;
	private Color color;

	public Point(int x, int y) {
		this.x = x;
		this.y = y;
	}

	public void setLocation(int x, int y) {
		this.x = x;
		this.y = y;
	}

	public void setColor(Color color) {
		this.color = color;
	}
}
public aspect PointAspect extends ObserverProtocol {

	// ... 省略

	protected pointcut subjectChange() :
		call( void Point.setLocation(int, int) ) ||
		call( void Point.setColor(Color) );
}
public class Client {

	public static void main(String[] args) {

		Point p = new Point(1, 1);

		p.addObserver( new Screen() );

		p.setLocation(5, 5);

		p.setColor(Color.RED);
	}
}
Screen: dp.observer.Point@187aeca
Screen: dp.observer.Point@187aeca

段階 - その 7 : 完成

public class Point {

	private int x;
	private int y;
	private Color color;

	public Point(int x, int y) {
		this.x = x;
		this.y = y;
	}

	public void setLocation(int x, int y) {
		this.x = x;
		this.y = y;
	}

	public void setColor(Color color) {
		this.color = color;
	}
}
public abstract aspect ObserverProtocol {

	protected interface Subject { }

	protected interface Observer {
		public void update();
	}

	private List Subject.observers = new ArrayList();

	public void Subject.addObserver(Observer observer) {
		observers.add(observer);
	}

	public void Subject.notifyObservers() {
		for(Iterator itr = observers.iterator() ; itr.hasNext(); ) {
			( (Observer)itr.next() ).update();
		}
	}

	protected pointcut update() : call( void Observer.update() );

	protected abstract pointcut subjectChange();

	after(Subject subject) returning : subjectChange() && target(subject) {
		subject.notifyObservers();
	}
}
public aspect PointAspect extends ObserverProtocol {

	declare parents : Point  implements Subject;
	declare parents : Screen implements Observer;

	private void Screen.update(Point point) {
		System.out.println("Screen: " + point);
	}

	private pointcut updateCall(Point point, Screen screen) :
		this(point) && target(screen) && update();

	void around(Point point, Screen screen) : updateCall(point, screen) {
		screen.update(point);
	}

	protected pointcut subjectChange() :
		call( void Point.setLocation(int, int) ) ||
		call( void Point.setColor(Color) );
}
public class Client {

	public static void main(String[] args) {

		Point p = new Point(1, 1);

		p.addObserver( new Screen() );

		p.setLocation(5, 5);

		p.setColor(Color.RED);
	}
}

実装 - その 6

(AspectJ 1.1 OK)

文献 [3] をもとに実装。

public class Subject {

	private String state;

	public void setState(String state) {
		this.state = state;
	}

	public pointcut stateSeted(Subject subject) :
		call( void Subject.setState(String) ) && target(subject);
}
public aspect ConcreteObserver {
	before(Subject subject) : Subject.stateSeted(subject) {
		System.out.println(subject);
	}
}
public class Client {

	public static void main(String[] args) {

		Subject subject = new Subject();
		subject.setState("xxx");
	}
}
実行結果:
dp.observer.Subject@e53108

実装 - その 7

段階 - その 1

(AspectJ 1.1 OK)

public class Subject {

	private String state;

	public void setState(String state) {
		this.state = state;
	}
}
public class ConcreteObserver {

	public void update(Subject subject) {
		System.out.println(subject);
	}
}
public aspect ObserverMapping pertarget( target(ConcreteObserver) || target(Subject) ) {

	private ConcreteObserver observer;
	private Subject subject;

	public void ConcreteObserver.observe(Subject subject) {
		ObserverMapping.aspectOf(this).subject     = subject;
		ObserverMapping.aspectOf(subject).observer = this;
	}

	before() : call( void Subject.setState(String) ) {
		ConcreteObserver observer =
			ObserverMapping.aspectOf( thisJoinPoint.getTarget() ).observer;

		observer.update( ObserverMapping.aspectOf(observer).subject );
	}
}
public class Client {

	public static void main(String[] args) {

		Subject subject = new Subject();
		ConcreteObserver observer = new ConcreteObserver();
		observer.observe(subject);

		subject.setState("xxx");
	}
}
実行結果:
dp.observer.Subject@1d9dc39

段階 - その 2 : リファクタリング

(AspectJ 1.1 OK)

public aspect ObserverMapping pertarget( target(Subject) ) {

	private ConcreteObserver observer;
	private Subject subject;

	public void ConcreteObserver.observe(Subject subject) {
		ObserverMapping.aspectOf(subject).observer = this;
		ObserverMapping.aspectOf(subject).subject  = subject;
	}

	before() : call( void Subject.setState(String) ) {
		this.observer.update( this.subject );
	}
}
このままでの実装では、ある subject オブジェクトが observe されていないと、NullPointerException がスローされてしまうという問題点があります:
public class Client {

	public static void main(String[] args) {

		Subject subject = new Subject();
		subject.setState("xxx"); // NullPointerException
	}
}

段階 - その 3 : バグの修正

(AspectJ 1.1 OK)

public aspect ObserverMapping pertarget( target(Subject) ) {

	private ConcreteObserver observer;
	private Subject subject;

	public void ConcreteObserver.observe(Subject subject) {
		ObserverMapping.aspectOf(subject).observer = this;
		ObserverMapping.aspectOf(subject).subject  = subject;
	}

	private static boolean isObserved(Subject subject) {
		return ObserverMapping.aspectOf(subject).observer != null;
	}

	before(Subject subject) :
		target(subject) && 
		call( void Subject.setState(String) ) && if ( isObserved( subject ) )
	{
		this.observer.update( this.subject );
	}
}

段階 - その 4 :

(AspectJ 1.1 OK)

現在の実装では、以下のコードで示しているように、1 つの observer オブジェクトが複数の subject オブジェクトの状態を観察することをサポートしています:

public class Client {

	public static void main(String[] args) {

		Subject subject1 = new Subject();
		Subject subject2 = new Subject();


		ConcreteObserver observer = new ConcreteObserver();

		observer.observe(subject1);
		observer.observe(subject2);


		subject1.setState("xxx");
		subject2.setState("yyy");
	}
}
dp.observer.Subject@19189e1
dp.observer.Subject@1f33675
しかし、ある subject オブジェクトが複数の observer オブジェクトによってその状態を観察できるような機能はサポートしていません:
public class Client {

	public static void main(String[] args) {

		Subject subject = new Subject();

		ConcreteObserver observer1 = new ConcreteObserver();
		ConcreteObserver observer2 = new ConcreteObserver();

		observer1.observe(subject);
		observer2.observe(subject);


		subject.setState("xxx");
	}
}
dp.observer.Subject@19189e1
この要求に対して、すぐに思い浮かぶ単純な解決策としては、リストを使うというものがあります:
import java.util.*;

public aspect ObserverMapping pertarget( target(Subject) ) {

	private List observers = new ArrayList();
	private Subject subject;

	public void ConcreteObserver.observe(Subject subject) {
		ObserverMapping.aspectOf(subject).observers.add( this );
		ObserverMapping.aspectOf(subject).subject  = subject;
	}

	before() : call( void Subject.setState(String) ) {

		for(Iterator itr = observers.iterator(); itr.hasNext();) {
			( (ConcreteObserver)itr.next() ).update(subject);
		}
	}
}

実装 - その 8

インスタンスレベルアスペクト を使用しての実装。

public class Point {

	private int x;
	private int y;

	public Point(int x, int y) {
		this.x = x;
		this.y = y;
	}
	
	public void setLocation(int x, int y) {
		this.x = x;
		this.y = y;
		
		System.out.println("setLocation - [" + x + ", " + y + "]");
	}
}
import instancelevel.InstanceLevelAspect;

public aspect PointObserver extends InstanceLevelAspect {

	public static PointObserver newInstance() { return null; };

	public void observe(Point p) {
		addObject(p);
	}

	after() : call( void Point.setLocation(int, int) ) {

		System.out.println("updated - " + this);
	}

	private static aspect TargetCheckImpl extends TargetCheck {
	
		protected pointcut adviceTarget() : target(Point);
	}

}
public class Main {

	public static void main(String[] args) {

		Point p = new Point(1, 2);
		p.setLocation(2, 3);


		PointObserver o1 = PointObserver.newInstance();
		PointObserver o2 = PointObserver.newInstance();


		o1.observe(p);

		p.setLocation(10, 20);


		o2.observe(p);

		p.setLocation(100, 200);
	}
}
実行結果:
setLocation - [2, 3]
setLocation - [10, 20]
updated - aj.PointObserver@9931f5
setLocation - [100, 200]
updated - aj.PointObserver@9931f5
updated - aj.PointObserver@1f1fba0

参考文献

更新履歴

todo

[
戻る ]