asato <asato@ncfreak.com>
最終更新日 : 2004/4/25 (2002/9/26 より)
テスト環境
Factory Method
Factory Method の問題点
改善できる点
AspectJ を使った Factory Method に関するテクニックの種類
ConcreteProduct の生成場所の制限
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 >> |
要望:
StandardMazeCreator クラスがあるとして、このクラスに対してクライアントが希望することは "標準的な迷路" を生成する、ということである。もし、異なる ConcreteProdcut を生成することによって StandardMazeCreator が BombedMazeCreator となるような ConcreteProduct を生成するつもりなら、その意図は失われる。
異なる ConcreteProdcut を生成するとしても、その Creator の意図を変更しないことが条件となる。"標準" はアプリケーションによって異なるかもしれないため、そのための ConcreteProduct を生成するなど。
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 { } }
メモ
適用可能性:
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 のときの出力妥協点:
public class Client { public static void main(String[] args) { MazeCreator creator = new MazeCreator(); Maze maze = creator.makeMaze(); // ファクトリメソッドの直接的な呼び出し System.out.println( maze.getClass() ); } ... }このようなコーディングを避けるために使える1つの方法は、コンパイル時に、そのようなコーディングをエラーとして発生させるようなコードをアスペクトを組み込むことである:
public aspect MazeAspect { ... pointcut illegalFactoryMethodCall() : !within(MazeCreator || MazeAspect) && call(Maze MazeCreator.makeMaze() ); declare error : illegalFactoryMethodCall() : "!withincode(Maze MazeCreator.createMaze() ) "; }AspectJ の提供している機能の1つである declare error を使うことで、上記のようなカスタム・コンパイル・エラーを任意に作ることができる。これにより、たとえ不適切な位置でファクトリメソッドを呼び出すようなコードを書いたとしても コンパイル時に、その意図されていないようなファクトリメソッドの呼び出しを即座に検出することが可能となる。
実装 - その 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(); } }
疑問
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 を用いて
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
文献 [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
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
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