Upload page content

You can upload content for the page named below. If you change the page name, you can also upload content for another page. If the page name is empty, we derive the page name from the file name.

File to load page content from
Page name
Comment

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