簡體   English   中英

如何使用Guice創建遞歸對象圖?

[英]How to create recursive object graphs with Guice?

假設我有5個類A,B,C,D,E,它們都實現了一個公共接口X.B,C,D和E類中的每一個都有一個X類型的字段(所以它們可以看作是包裝類)。

創建哪些實例是在運行時確定的,因此我可以使用以下對象圖之一:

E -> D -> C -> B -> A
D -> B -> A
E -> A
A

(訂單是固定的,最里面的實例總是類型A,但是沒有限制。)

不,我想用Guice創建這個對象,以避免手工提供所有其他依賴項。

最簡單的方法是什么? 目前似乎我必須這樣做

  1. 讓Guice創建A的實例
  2. 創建一個模塊,將X.class綁定到此實例,並使用此附加模塊創建子注入器
  3. 讓Guice創建下一個實例(例如,類型B)
  4. 重復2.,現在將X.class綁定到B的實例
  5. 重復3.和4.直到創建所有對象

有沒有更簡單的方法? 我是否可能以某種方式自動注冊X的子類的新實例作為每次創建X的綁定?

編輯:澄清“ 在運行時確定創建了哪些實例 ”:

我當前的代碼如下所示:

X createX() {
  X x = new A(dependencies);
  if (useB) x = new B(x, some, dependencies);
  if (useC) x = new C(x, further, dependencies);
  if (useD) x = new D(x, dependency);
  if (useE) x = new E(x, yet, other, dependencies);
  return x;
}

標志useB,useC,useD和useE的值來自屬性文件。

我的主要目標是手動保存為構造函數提供所有依賴項。

編輯:解決方案:

我添加了自己的解決方案,我在此期間找到了解決方案。 感謝所有的回答者!

改進我的解決方案的一種方法是可以刪除構造函數參數上的@InInstance注釋。 我已經嘗試過類型監聽器,但我還沒有找到辦法。 提示是受歡迎的。

我已經為這個問題開發了以下解決方案:

首先,我創建一個標記注釋@InInstance ,它將Class對象作為值。 我使用這個注釋來注釋所有包裝類中的類型X的參數(即B到E)。 例:

class B {
  B(@InInstance(B.class) X x,
    Other dependencies) {
  ...
  }
}

現在,如果我想要一個完整鏈的實例(A到E),我綁定

  • X.class到E.class
  • X.class annotatedWith InInstance(E.class)到D.class
  • ...
  • X.class annotatedWith InInstance(B.class)到A.class

然后調用createInstance(X.class)

這解決了讓Guice創建我的實例並提供依賴關系的問題。

現在針對根據屬性文件在運行時創建適當綁定的問題,我創建了一個Guice擴展,允許我創建“包裝器綁定”。 這樣的綁定需要一個類型(這里是X),一個包裝器實現類型(這里是B到E之一)和一個基本模塊,它需要包含X的綁定。然后它創建一個包含兩個綁定的新模塊:

  • 與基本模塊中的X相同的綁定,但是帶annotatedWith(InInstance(wrapper type))
  • 從X到包裝器類型的綁定

然后可以使用這樣的模塊來覆蓋基本模塊與Modules.override(base module).with(wrapper module)的綁定。

使用一些不錯的Guice風格的EDSL,它看起來像:

Module baseModule = ... // contains binding for X to A

if (useB) {
  Module wrapperModule = new ChainingModule() {
    void configure() {
      wrap(X.class).from(baseModule).with(B.class); // possible to use .in(...) etc. here
      // In this case, this line is equivalent to

      // bind(X.class).annotatedWith(InInstance(B.class)).to(A.class);
      // bind(X.class).to(B.class);

      // It has the advantage of automatically looking up the current binding
      // for X in the base module, which makes it easy to chain this.
    }
  }
  baseModule = Modules.override(baseModule).with(wrapperModule);
}

...

對於常見情況,有一些輔助方法,它看起來像:

Module module = ... // contains binding for X to A
if (useB) {
  module = Chaining.wrap(X.class).from(module).with(B.class);
}
if (useC) {
  module = Chaining.wrap(X.class).from(module).with(C.class);
}
if (useD) {
  module = Chaining.wrap(X.class).from(module).with(D.class);
}
if (useE) {
  module = Chaining.wrap(X.class).from(module).with(E.class);
}

Injector injector = Guice.createInjector(module);
X x = injector.createInstance(X.class);

每行創建適當的包裝器模塊並返回包含給定模塊的新模塊,其中一些綁定由包裝器模塊覆蓋。

“在運行時”增加了相當多的復雜性。 我假設這意味着你創建的每個 X實例,你對包裝A的對象鏈做出一些決定。

如果你在施工期間沒有將X注入E,D,C和B怎么樣? 而是添加setNext(X)方法。 我不確定這對你有多現實,但是聽我說。 您可以創建如下工廠:

class XFactory {
  @Inject Provider<A> a;
  @Inject Provider<B> b;
  @Inject Provider<C> c;
  @Inject Provider<D> d;
  @Inject Provider<E> e;
  public X create(Data state) {
    // the 'state' object is just whatever information you use to 
    // decide what objects you need to create;
    X result = a.get();
    if(state.useB()) {
      B head = b.get();
      head.setNext(result);
      result = head;
    }
    if(state.useC()) {
      C head = c.get();
      head.setNext(result);
      result = head;
    }
    if(state.useD()) {
      D head = d.get();
      head.setNext(result);
      result = head;
    }
    if(state.useE()) {
      E head = e.get();
      head.setNext(result);
      result = head;
    }
    return result;
  }
}

你可以考慮引入一個像DelegatingX或WrapperX這樣的接口來保存setNext()方法。 它可能會減少一些重復。

此外,如果“在運行時”實際上是應用程序啟動,因此X的每個實例應該是相同的對象鏈,那么您還有一個選項:

class MyModule extends AbstractModule {
  public void configure() {
    Multibinder<DelegatingX> chain
        = Multibinder.newSetBinder(binder(), DelegatingX.class);
    if(useB()) chain.addBinding().to(B.class);
    if(useC()) chain.addBinding().to(C.class);
    if(useD()) chain.addBinding().to(D.class);
    if(useE()) chain.addBinding().to(E.class);
  }
  @Provides X provideX(Set<DelegatingX> chain, A a) {
    X result = a;
    // 'item' is B, then C...
    for(X item : chain) {
      item.setNext(result);
      result = item;
    }
    return result; // this is E
  }
}

我知道這個答案非常抽象,但我希望它能讓你更接近解決方案。

我還發現你的定義中的“運行時”部分不清楚。

但是,讓我們假設我們首先想象這些鏈是如何在沒有Guice的情況下制作的。

class chainHolder {
  public final X chain1 = new E(new D(new C(new B(new A()))));
  public final X chain2 = new D(new B(new A()));
  public final X chain3 = new E(new  A());
  public final X chain4 = new A();
}

但是你的問題是,你想要注入的每個構造函數中還有很多其他東西要傳遞給你嗎? 好的,讓我們為這些設置一些注入:

//Pseudo multi-class def here
classes B...E extends X {
  private final X nextInChain;
  @Inject
  public B...E(TheStuffINeedToo NotMentionedAbove, X nextInChain) {
    super(TheStuffINeedToo);
    this.nextInChain = nextInChain;
  }
}

class A extends X {
  @Inject
  public A(TheStuffINeedToo NotMentionedAbove) {
    super(TheStuffINeedToo);
  }
}

class chainHolder {
  public final X chain1;
  public final X chain2;
  public final X chain3;
  public final X chain4;
  @Inject
  public chainHolder(@Chain1 X c1, @Chain2 X c2, @Chain3 X c3, @Chain4 X c4) {
    chain1 = c1;
    chain2 = c2;
    chain3 = c3;
    chain4 = c4;
  }
}

class chainModlule extends AbstractModule {
  public void configure() {
    //The other stuff you wanted provided gets bound here.
    bind(TheStuffINeedToo.class).to(TheStuffNotMentionedInTheQuestion.class);
    //Maybe A should always be the same terminating instance.
    bind(A).in(Singleton.class);
    bind(X).annotatedWith(Chain4.class).to(A.class);
  }

  @Provides @Chain1
  public X providesChain1X(@Chain1 Provider<E> e) {
    return e.get();
  }

  @Provides @Chain1
  public E providesChain1E(@Chain1 Provider<D> d, TheStuffINeedToo stuff) {
    return new E(stuff, d.get());
  }

  @Provides @Chain1
  public D providesChain1D(@Chain1 Provider<C> c, TheStuffINeedToo stuff) {
    return new D(stuff, c.get());
  }

  @Provides @Chain1
  public C providesChain1C(@Chain1 Provider<B> b, TheStuffINeedToo stuff) {
    return new C(stuff, b.get());
  }

  @Provides @Chain1
  public B providesChain1B(Provider<A> a, TheStuffINeedToo stuff) {
    return new B(stuff, a.get());
  }

 // ... Similarly for chain2 then ...

  @Provides @Chain3
  public X providesChain3X(@Chain3 Provider<E> e) {
    return e.get();
  }

  @Provides @Chain3
  public E providesChain3E(Provider<A> a, TheStuffINeedToo stuff) {
    return new E(stuff, a.get());
  }
}

現在。 你看,這不是保存/很多/工作。 如果所有的類都采用了你想要注入的相同的東西(比如我的偽設置),那么你可以將整個鏈轉移到一個提供者並重用那個東西實例,或者提供東西。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM