Locked History Actions

guice/RobotLegsProblem

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