Robot Legs Problem
ロボットの足問題とは次のようなものである。 ロボットというオブジェクトを取得したい。ロボットには脚が二本ついており、二つは全く同じオブジェクトであるが、しかし足部(足首から下)は左右異なるものにしたい。
PrivateModule以前の解決法
PrivateModule以前の解決法では、おそらく以下のようになると思われる。
Robotには二つのLegを注入するが、@Left, @Rightというアノテーションによって右脚実装か左脚実装かを選択する。 それぞれの脚実装には右足(足首から下)、左足(足首から下)が注入される。
本来、脚部分は一つの実装で十分であるにも関わらず、異なる足部を注入するためには、別々の実装としなければならないので、要件には適合してはいない。
public class TestZero { @Retention(RetentionPolicy.RUNTIME) @Target({FIELD, PARAMETER, METHOD}) @BindingAnnotation @interface Right {} @Retention(RetentionPolicy.RUNTIME) @Target({FIELD, PARAMETER, METHOD}) @BindingAnnotation @interface Left {} /** 脚部 */ public interface Leg {} /** 右用足(足首から下)のついた脚 */ public static class RightLegImpl implements Leg { private final RightFoot foot; @Inject public RightLegImpl(RightFoot foot) { this.foot = foot; } @Override public String toString() { return "Leg with " + foot; } } /** 左用足(足首から下)のついた脚 */ public static class LeftLegImpl implements Leg { private final LeftFoot foot; @Inject public LeftLegImpl(LeftFoot foot) { this.foot = foot; } @Override public String toString() { return "Leg with " + foot; } } /** 足(足首から下) */ public interface Foot {} /** 右用の足 */ public static class RightFoot implements Foot { @Override public String toString() { return "RightFoot"; } } /** 左用の足 */ public static class LeftFoot implements Foot { @Override public String toString() { return "LeftFoot"; } } /** ロボット */ public static class Robot { @Inject @Right Leg rightLeg; @Inject @Left Leg leftLeg; @Override public String toString() { return "" + rightLeg + ", " + leftLeg; } } public static void main(String[] args) { Injector injector = Guice.createInjector( new AbstractModule() { @Override protected void configure() { bind(Leg.class).annotatedWith(Right.class).to(RightLegImpl.class); bind(Leg.class).annotatedWith(Left.class).to(LeftLegImpl.class); } }); Robot robot = injector.getInstance(Robot.class); System.out.println("" + robot); } }
実行結果は、
Leg with RightFoot, Leg with LeftFoot
PrivateModule以前の解決法2
あるいは以下のような解決法を用いることも可能だ。 この場合、ロボットの中で脚と足を組み立てることになる。
脚の実装は一つなので要件には適合しているが、組み立てが面倒かもしれない。 このような単純な場合はそれほどでもないかもしれないが。
public class TestOne { /** 脚部 */ public interface Leg { public void setFoot(Foot foot); } /** 脚部実装 */ public static class LegImpl implements Leg { private Foot foot; public void setFoot(Foot foot) { this.foot = foot; } @Override public String toString() { return "Leg with " + foot; } } /** 足(足首から下) */ public interface Foot {} /** 右用の足 */ public static class RightFoot implements Foot { @Override public String toString() { return "RightFoot"; } } /** 左用の足 */ public static class LeftFoot implements Foot { @Override public String toString() { return "LeftFoot"; } } /** ロボット */ public static class Robot { Leg rightLeg; Leg leftLeg; @Inject public Robot( Leg rightLeg, RightFoot rightFoot, Leg leftLeg, LeftFoot leftFoot) { this.rightLeg = rightLeg; this.leftLeg = leftLeg; rightLeg.setFoot(rightFoot); leftLeg.setFoot(leftFoot); } @Override public String toString() { return "" + rightLeg + ", " + leftLeg; } } public static void main(String[] args) { Injector injector = Guice.createInjector( new AbstractModule() { @Override protected void configure() { bind(Leg.class).to(LegImpl.class); } }); Robot robot = injector.getInstance(Robot.class); System.out.println("" + robot); } }
PrivateModuleでの解決法
PrivateModuleを使うと以下のようになる。 @Right/@Leftというアノテーションを付加する対象はLegであるにも関わらず、これはLegの実装の選択ではなくなる。 Legの実装はただ一つであり、そこに注入されるFootの実装を選択することになる。 これによって、部品およびロボットの実装は単純になる。
が、その代わりにモジュールが複雑になることは否めないが。
public class TestTwo { @Retention(RetentionPolicy.RUNTIME) @Target({FIELD, PARAMETER, METHOD}) @BindingAnnotation @interface Right {} @Retention(RetentionPolicy.RUNTIME) @Target({FIELD, PARAMETER, METHOD}) @BindingAnnotation @interface Left {} /** 脚部 */ public interface Leg {} /** 脚部左右共通 */ public static class LegImpl implements Leg { private final Foot foot; @Inject public LegImpl(Foot foot) { this.foot = foot; } @Override public String toString() { return "Leg with " + foot; } } /** 足(足首から下) */ public interface Foot {} /** 右用の足 */ public static class RightFoot implements Foot { @Override public String toString() { return "RightFoot"; } } /** 左用の足 */ public static class LeftFoot implements Foot { @Override public String toString() { return "LeftFoot"; } } /** ロボット */ public static class Robot { @Inject @Right Leg rightLeg; @Inject @Left Leg leftLeg; @Override public String toString() { return "" + rightLeg + ", " + leftLeg; } } public static void main(String[] args) { Injector injector = Guice.createInjector( new PrivateModule() { @Override protected void configure() { bind(Leg.class).annotatedWith(Right.class).to(LegImpl.class); expose(Leg.class).annotatedWith(Right.class); bind(Foot.class).to(RightFoot.class); } }, new PrivateModule() { @Override protected void configure() { bind(Leg.class).annotatedWith(Left.class).to(LegImpl.class); expose(Leg.class).annotatedWith(Left.class); bind(Foot.class).to(LeftFoot.class); } }); Robot robot = injector.getInstance(Robot.class); System.out.println("" + robot); } }