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);
}
}