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

asato <asato@ncfreak.com>

最終更新日 : 2004/4/25 (2002/9/26 より)

テスト環境

Factory Method

オブジェクトを生成するときのインタフェースだけを規定して、実際にどのクラスをインスタンス化するかはサブクラスが決めるようにする。Factory Method パターンは、インスタンス化をサブクラスに任せる。

Factory Method の問題点

改善できる点

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

ConcreteProduct の生成場所の制限

アスペクトを使うことによって ConcreteProduct の生成を Creator 内でのみ行えるように制限する。
public class ProductID {
	public static final ProductID PRODUCT_A = new ProductID();
	public static final ProductID PRODUCT_B = new ProductID();

	private ProductID() {}
}
public class Creator {

	public Product createProduct(int id) {
		if (id == ProductID.PRODUCT_A) {
			return new ConcreteProductA();

		} else if (id == ProductID.PRODUCT_B) {
			return new ConcreteProductB();

		} else {
			throw new NullPointerException("id == null");
		}
	}
}
public class Product { }
public class ConcreteProductA { }
public class ConcreteProductB { }
public class Client {
	public static void main(String[] args) {
		Creator creator = new Creator();
		Product p1 = creator.createProduct( ProductID.PRODUCT_A );
	
		Product p2 = new ConcreteProductB(); // コンパイル時にエラーとして検出される
	}
}
public aspect CreatorAspect {
	pointcut illegalCreate() :
		!within(Creator) && call( Product+.new(..) );

	declare error: illegalCreate() : "!within(Creator)";
}

構成要素
クラス 役割
Creator
ConcreteCreator
Product
ConcreteProduct
クラス/アスペクト名 ソースコードの要求 役割
Client クライアント
MazeAspect ・<< FacM-ConcreteProduct >> クラスの定義
・around advice を使った << FacM-factoryMethod >> の書き換え
MazeCreator << FacM-Creator >>
Maze << FacM-Product >>

要望:

条件: 結果: どこに再利用性を求めるのか:
public class Client {
	public static void main(String[] args) {
		MazeCreator creator = new MazeCreator();
		Maze maze = creator.createMaze();

		System.out.println( maze.getClass() );
	}
}
public class MazeCreator {

	public Maze createMaze() {
		Maze maze = makeMaze();

		return maze;
	}
	protected Maze makeMaze() {
		return new Maze();
	}
}
public class Maze {}
public aspect MazeAspect {
	pointcut makeMaze() : execution( Maze MazeCreator.makeMaze() );

	Maze around() : makeMaze() {
		return new MyMaze();
	}
	private class MyMaze extends Maze {
	}
}

メモ

new 演算子によるハードコーディング的なオブジェクトの生成を和らげる。

適用可能性:

public class MazeCreator {

	public Maze createMaze() {
		Maze maze = new Maze();

		return maze;
	}
}
public aspect MazeAspect {
	pointcut newMaze() : call( Maze.new() );

	Maze around() : newMaze() {
		return new MyMaze();
	}

	private class MyMaze extends Maze {}
}

メモ 2 : ファクトリメソッドの導入

注意:パターン形式っぽく書いてありますが、パターンでは ありません。一度も実践で使った経験はないです。

意図: Creator の候補となりうるが、Prodcut オブジェクトを生成するためのファクトリメソッドを持っていないようなクラスに新たにファクトリメソッドを導入する。これにより、Creator のサブクラスはその新たに導入されたファクトリメソッドをオーバーライドすることが可能になり、ファクトリメソッドをオーバーライドすることで特定の Prodcut オブジェクトを生成することができるようになる。

問題:Creator クラスとして扱いたいが、ファクトリメソッドを通して Product オブジェクトを生成して いない Creator クラス候補が存在する。そのための一番の方法は、恐らくリファクタリングを通してその Creator クラス候補にファクトリメソッドを新たに定義し、そしてそのファクトリメソッドを通して Product オブジェクトを生成するように変更することであるが、Creator クラスのソースコードを変更したくない何らかの理由がある。しかし、その不完全な Creator クラスに代わるクラスを新たに開発することは避けたい。

適用可能性:

構成要素
クラス/アスペクト名 ソースコードの要求 役割/責任/状況
Client クライアント
Creator 不完全な << FacM-Creator >>。Product オブジェクトを new 演算子を使ってハードコーディング的に生成している。したがって、サブクラスでオーバーライドされることが意図されるようなファクトリメソッドを定義していない。
ConcreteCreator << FacM-ConcreteCreator >>
Prodcut << FacM-Product >>
ConcreteProdcut << FacM-ConcreteProduct >>
CreatorAspect ・Creator にファクトリメソッドを導入する。
・ファクトリメソッドの呼び出しのスコープをコントロールする。

実装例:

public class MazeCreator {

	public Maze createMaze() { // 注:このメソッドはファクトリメソッドではない
                                      // AnOperation に対応
		Maze maze = new Maze(); // Maze が Product に対応する

		return maze;
	}
}
public class Maze {
}
public aspect MazeAspect {
	pointcut makeMaze(MazeCreator creator) :
		withincode(Maze MazeCreator.createMaze() ) &&
		this(creator) &&
		call( Maze.new() );

	Maze around(MazeCreator creator) : makeMaze(creator) {
		return creator.makeMaze();
	}

	public Maze MazeCreator.makeMaze() {
		return new Maze();
	}
}

public class Client {
	public static void main(String[] args) {

		MazeCreator creator = new MazeCreator();
		Maze maze = creator.createMaze();

		System.out.println( maze.getClass() );
	}

	public static class MyMazeCreator extends MazeCreator {
		public Maze makeMaze() {
			return new MyMaze();
		}

		public static class MyMaze extends Maze {}
	}
	// 以下の ClientAspect は直接は関係ない
	public static aspect ClientAspect {
	
		private static boolean doAspect = false; // true なら MyMazeCreator,
		                                           // false なら MazeCreator
	
		pointcut newCreator(): 
			if (doAspect == true) && call( MazeCreator.new() );

		MazeCreator around() : newCreator() {
			return new MyMazeCreator();
		}
	}
}
class dp.Maze                        // ClientAspect で doAspect = false のときの出力
class dp.Client$MyMazeCreator$MyMaze // ClientAspect で doAspect = true のときの出力
妥協点:

実装 - その 3

段階 - その 1

public class Maze {

	private Room room;

	public void init() {
		this.room = new Room();
	}
	
	public Room getRoom() {
		return room;
	}
}
public class Client {

	public static void main(String[] args) {

		Maze maze = new Maze();
		maze.init();

		System.out.println( maze.getRoom() );
	}
}
public aspect RoomFactory {

	Room around() : call( Room.new() ) && withincode( void Maze.init() ) {
		return new MyRoom();
	}
	
	private static class MyRoom extends Room { }
}
dp.fm.RoomFactory$MyRoom@1cd2e5f

段階 - その 2

public abstract aspect RoomFactory {

	Room around() : call( Room.new() ) && withincode( void Maze.init() ) {
		return createRoom();
	}

	protected abstract Room createRoom();
}
public aspect MyRoomFactory extends RoomFactory {

	protected Room createRoom() {
		return new MyRoom();
	}
	
	private static class MyRoom extends Room { }
}
dp.fm.MyRoomFactory$MyRoom@19f953d

実装 - その 4

段階 - その 1

public class Maze {

	private Room room;

	public void init() {
		this.room = new Room();
	}
	
	public Room getRoom() {
		return room;
	}
}
public class MyMaze extends Maze {

	private static aspect RoomFactory {

		private pointcut newRoom() :
			this(MyMaze) &&
			call( Room.new() ) && cflow( call( void Maze.init() ) );

		Room around() : newRoom() {
			return new MyRoom();
		}
	}
	
	private static class MyRoom extends Room { }
}
public class Client {

	public static void main(String[] args) {

		Maze maze = new MyMaze();
		maze.init();

		System.out.println( maze.getRoom() );
	}
}
dp.fm.MyMaze$MyRoom@e48e1b

段階 - その 2

public abstract class MyAbstractMaze extends Maze {

	private static aspect RoomFactory {

		private pointcut newRoom(MyAbstractMaze maze) :
			this(maze) &&
			call( Room.new() ) && cflow( call( void Maze.init() ) );

		Room around(MyAbstractMaze maze) : newRoom(maze) {
			return maze.createRoom();
		}
	}
	
	protected abstract Room createRoom();
}
public class MyConcreteMazeA extends MyAbstractMaze {

	protected Room createRoom() {
		return new MyRoom();
	}

	private static class MyRoom extends Room { }
}
public class MyConcreteMazeB extends MyAbstractMaze {

	protected Room createRoom() {
		return new MyRoom();
	}

	private static class MyRoom extends Room { }
}
public class Client {

	public static void main(String[] args) {

		Maze maze1 = new MyConcreteMazeA();
		Maze maze2 = new MyConcreteMazeB();

		maze1.init();
		maze2.init();

		System.out.println( maze1.getRoom() );
		System.out.println( maze2.getRoom() );
	}
}
dp.fm.MyConcreteMazeA$MyRoom@42719c
dp.fm.MyConcreteMazeB$MyRoom@30c221

疑問

段階 - その 3 : もし RoomFactory アスペクトを分割して定義するとしたら?

public aspect RoomFactory {

	private pointcut newRoom(MyAbstractMaze maze) :
		this(maze) &&
		call( Room.new() ) && cflow( call( void Maze.init() ) );

	Room around(MyAbstractMaze maze) : newRoom(maze) {
		return maze.createRoom();
	}
}
public abstract class MyAbstractMaze extends Maze {

	protected abstract Room createRoom();
}

段階 - その 4 : RoomFactory アスペクトは一般化できるか?

public abstract aspect ProductFactory {

	protected interface Creator { }

	public abstract Object Creator.create();


	protected abstract pointcut operation();
	protected abstract pointcut product();

	private pointcut newProdcut(Creator creator) :
		this(creator) &&
		product() && cflow( operation() );


	Object around(Creator creator) : newProdcut(creator) {
		return creator.create();
	}
}
public aspect RoomFactory extends ProductFactory {

	declare parents: MyAbstractMaze implements Creator;

	protected pointcut operation() : call( void Maze.init() );
	protected pointcut product()   : call( Room.new() );

	public Object MyAbstractMaze.create() {
		return createRoom();
	}
}

疑問

1 つ目の疑問に対しては:
Maze maze = new MyConcreteMazeA();
maze.create(); // コンパイルエラー。Maze には create メソッドは定義されていない。
MyAbstractMaze maze = new MyConcreteMazeA();
maze.create(); // OK. しかし、外部から呼び出せるのは困る。

段階 - その 5 : 複数の Product 生成を考慮する

public class Door { }
public class Room {

	private Door door;

	public void setDoor(Door door) {
		this.door = door;
	}
	public Door getDoor() {
		return door;
	}
}
public class Maze {

	private Room room;

	public void init() {
	
		this.room = new Room();

		room.setDoor( new Door() );
	}
	
	public Room getRoom() {
		return room;
	}
}
public abstract class MyAbstractMaze extends Maze {
	protected abstract Room createRoom();
	protected abstract Door createDoor();
}
public class MyConcreteMazeA extends MyAbstractMaze {

	protected Room createRoom() {
		return new MyRoom();
	}

	protected Door createDoor() {
		return new MyDoor();
	}

	private static class MyRoom extends Room { }

	private static class MyDoor extends Door { }
}
public class MyConcreteMazeB extends MyAbstractMaze {

	protected Room createRoom() {
		return new MyRoom();
	}

	protected Door createDoor() {
		return new MyDoor();
	}

	private static class MyRoom extends Room { }

	private static class MyDoor extends Door { }
}
public aspect MazeFactory {

	private pointcut init(MyAbstractMaze maze) :
		this(maze) && cflow( call( void Maze.init() ) );

	Room around(MyAbstractMaze maze) : call( Room.new() ) && init(maze) {
		return maze.createRoom();
	}
	Door around(MyAbstractMaze maze) : call( Door.new() ) && init(maze) {
		return maze.createDoor();
	}
}
public class Client {

	public static void main(String[] args) {

		Maze maze1 = new MyConcreteMazeA();
		Maze maze2 = new MyConcreteMazeB();

		maze1.init();
		maze2.init();
		
		System.out.println( maze1.getRoom() );
		System.out.println( maze1.getRoom().getDoor() );

		System.out.println( maze2.getRoom() );
		System.out.println( maze2.getRoom().getDoor() );
	}
}
dp.fm.MyConcreteMazeA$MyRoom@119298d
dp.fm.MyConcreteMazeA$MyDoor@f72617
dp.fm.MyConcreteMazeB$MyRoom@1e5e2c3
dp.fm.MyConcreteMazeB$MyDoor@18a992f

実装 - その 5

public class Room { }
public class MyRoom extends Room { }
public class Maze {

	private Room room;

	public void init() {
	
		this.room = new Room();
	}
	
	public Room getRoom() {
		return room;
	}
}
public aspect MazeAspect {

	private Class Maze.clazz;

	public Maze.new(Class clazz) {
		this.clazz = clazz;
	}

	private pointcut newRoom(Maze maze) : 
		this(maze) && call( Room.new() ) && cflow( call( void Maze.init() ) );

	Object around(Maze maze) : newRoom(maze) {

		try {
			return maze.clazz.newInstance();

		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}
public class Client {

	public static void main(String[] args) {

		Maze maze = new Maze(MyRoom.class);
		maze.init();

		System.out.println( maze.getRoom() );
	}
}

実装 - その 6 : Virtual Class を用いて

(AspectJ 1.1.1 OK, virtualclass-aspect-0.0.1 OK)
import virtualclass.AbstractVirtualClassAspect;

public aspect VirtualClassAspect extends AbstractVirtualClassAspect {
	protected pointcut newVirtualClass() : call( *.*.new(..) );
}
public class Creator {

	public void operation() {

		Product prodcut = new Product();
		System.out.println(prodcut);
	}

	protected class Product { }
}
public class ConcreteCreator extends Creator {

	protected class Product extends Creator.Product { }
}
public class Client {

	public static void main(String[] args) {

		Creator creator1 = new Creator();
		Creator creator2 = new ConcreteCreator();

		creator1.operation();
		creator2.operation();
	}
}
実行結果:
dp.fm.Creator$Product@111a3ac
dp.fm.ConcreteCreator$Product@a83b8a

実装 - その 7

(AspectJ 1.1.1 OK)

文献 [1] より。

public interface Product { }
public class Creator {
	public Product create(String type) {
		return null;
	}
}
public class ConcreteProductA implements Product {
	
	private static aspect Impl {

		Product around(String type) :
			ProductCreation.create(type) && if ( type.equals("a") )
		{
			return new ConcreteProductA();
		}
	}
}
public class ConcreteProductB implements Product {

	private static aspect Impl {

		Product around(String type) :
			ProductCreation.create(type) && if ( type.equals("b") )
		{
			return new ConcreteProductB();
		}
	}
}
public class Client {

	public static void main(String[] args) {

		Creator creator = new Creator();
	
		System.out.println( creator.create("a") ); // dp.fm.ConcreteProductA@1f33675
		System.out.println( creator.create("b") ); // dp.fm.ConcreteProductB@1690726
	}
}
実行結果:
dp.fm.ConcreteProductA@1f33675
dp.fm.ConcreteProductB@1690726

実装 - その 8

(AspectJ 1.1.1 OK)
public interface Product { }
public class DefaultProduct implements Product { }
public class MyProduct implements Product {
public class Creator {

	public void operation() {
		System.out.println( createProduct() );
	}
	
	private Product createProduct() {
		return new DefaultProduct();
	}
}
public aspect MyProductAspect pertarget( target(Creator) ) {

	private boolean isActive;

	public void activate() {
		this.isActive = true;
	}

	Product around() : call(Product Creator.createProduct() ) {
		if (isActive) {
			return new MyProduct();
		} else {
			return proceed();
		}
	}
}
public class Client {

	public static void main(String[] args) {


		Creator creator = new Creator();

		creator.operation();


		MyProductAspect.aspectOf(creator).activate();

		creator.operation();
	}
}
実行結果:
dp.fm.DefaultProduct@1f33675
dp.fm.MyProduct@1690726

実装 - その 9

(AspectJ 1.2rc1 OK)

public interface Product { }
public class ConcreteProduct implements Product { }
public class Creator {

	public void operation() {

		Product product = createProduct();

		System.out.println( product );
	}
}
public aspect ConcreteProductCreation {

	public Product Creator.createProduct() {

		return new ConcreteProduct();
	}
}
public class Client {

	public static void main(String[] args) {


		Creator creator = new Creator();

		creator.operation();
	}
}
実行結果:
dp.fm.ConcreteProduct@1e63e3d

参考文献とリソース

参考文献: リソース:

更新履歴

todo

[
戻る ]