public class RealPayment implements Payment {
public RealPayment(
CreditService creditService, // from the Injector
AuthService authService, // from the Injector
Date startDate, // from the instance's creator
Money amount); // from the instance's creator
}
...
}
The standard solution to this problem is to write a factory that helps
Guice build the objects:
public interface PaymentFactory {
public Payment create(Date startDate, Money amount);
}
public class RealPaymentFactory implements PaymentFactory {
private final Provider<CreditService> creditServiceProvider;
private final Provider<AuthService> authServiceProvider;
public PaymentFactory(Provider<CreditService> creditServiceProvider,
Provider<AuthService> authServiceProvider) {
this.creditServiceProvider = creditServiceProvider;
this.authServiceProvider = authServiceProvider;
}
public Payment create(Date startDate, Money amount) {
return new RealPayment(creditServiceProvider.get(),
authServiceProvider.get(), startDate, amount);
}
}
Then in our module:
bind(PaymentFactory.class).to(RealPaymentFactory.class);
We think it's annoying to have to write the boilerplate factory class
each time we're in this situation. It's also annoying to have to
update the factories whenever our implementation class' dependencies
change.
This motivated Jerome Mourits and I to write a Guice extension called
AssistedInject. It generates the implementation of our Factory class
automatically. To use it, we annotate the implementation class'
constructor and the fields that aren't known by the Injector:
public class RealPayment implements Payment {
@AssistedInject
public RealPayment(
CreditService creditService,
AuthService authService,
@Assisted Date startDate,
@Assisted Money amount);
}
...
}
Then we reflectively create a Provider<Factory> in the Guice module:
bind(PaymentFactory.class).toProvider(
FactoryProvider.newFactory(PaymentFactory.class,
RealPayment.class));
Our FactoryProvider class maps the create() method's parameters to the
corresponding @Assisted parameters in the implementation class'
constructor. For the other constructor arguments, it asks the regular
Injector to provide values.
With the FactoryProvider class, we can easily to create classes that
need extra arguments at construction time:
1. Annotate the constructor and assisted parameters on the
implementation class (such as RealPayment.java)
2. Create a Factory interface with a create() method that takes only
the assisted parameters. Make sure they're in the same order as in the
constructor
3. Bind that FactoryInterface to our FactoryProvider interface.
The code is available as an open source Guice extension:
Source with Jar: http://publicobject.com/publicobject/assistedinject/assistedinject_20070615.zip
Javadocs: http://publicobject.com/publicobject/assistedinject/javadocs/index.html
I think this feature would really shine if you could support factories
that can create multiple related objects:
bind(FooFactory.class)
.toProvider(FactoryProvider.newFactory(BankFactory.class,
RealPayment.class, Loan.class, Insurance.class, /* more... */));
Thanks,
Robbie
> Source with Jar:http://publicobject.com/publicobject/assistedinject/assistedinject_20...
> Javadocs:http://publicobject.com/publicobject/assistedinject/javadocs/index.html
> - Why limit this to constructors?
Probably just 'cause we tend to prefer immutable objects that
set all their fields at construction time. Our code will get Guice
to do any necessary field and method injection if necessary, but
@Assisted is for constructors only.
> - Maybe there's a better name for "Assisted". "Provided" or something
> like that?
@Provided is weird because there's already a Provider interface that
means something different. If you can come up with other good names,
I'm interested. What we're really looking for is a concise way to
describe
that the object is being created by both the injector and by some
client
who needs an instance.
> I think this feature would really shine if you could support factories
> that can create multiple related objects:
That would probably work okay. One wrinkle is if the factory
methods return types don't cleanly map to a single implementation
class. Consider a Factory that builds Java collections classes:
interface CollectionFactory {
<T> Set<T> createSet(T... elements);
<T extends Comparable> SortedSet<T> createSortedSet(T...
elements);
}
If we'd like to use this with HashSet and TreeSet as our
respective implementations, then it won't know which
implementation to return for the createSet() method.
One thing we already do is support overloaded factory methods.
If you have say, createPayment(Date, Money) and also
createPayment(Money), then it looks for 2 constructors
annotated as @AssistedInject.
Thanks for the feedback Robbie. If you try it out and run into
any problems, let me know and we'll write a fix!
Cheers,
Jesse
On Jun 16, 8:13 pm, "j...@swank.ca" <limpbiz...@gmail.com> wrote:
...
> @Provided is weird because there's already a Provider interface that
> means something different. If you can come up with other good names,
> I'm interested.
Some I can think of right now:
Inject: @PartialInject, @HybridInject, @FactoryInject, @InjectSome,
@InjectKnown, ...
Parameters: @Manufactured, @Runtime, @Unknown, @DontInject, @DIY,
@Later, ...
> > I think this feature would really shine if you could support factories
> > that can create multiple related objects:
>
> That would probably work okay. One wrinkle is if the factory
> methods return types don't cleanly map to a single implementation
> class.
Interesting problem, indeed. Maybe you can do something like a return
type binding.
ReturnTypeBinding rtb =
bindReturnType(Payment.class).to(RealPayment.class);
bind(PaymentFactory.class).toProvider(FactoryProvider.newFactory(PaymentFactory.class,
rtb));
> Thanks for the feedback Robbie. If you try it out and run into
> any problems, let me know and we'll write a fix!
Will do!
> Cheers,
> Jesse
Thanks,
Robbie
Thanks for the suggestions. I like the idea of the both the
constructor annotation and the parameter annotation being similar.
Using one of your examples, maybe something like @FactoryInject and
@FromFactory. I think this might help unfamiliar users realize that
they are related.
As for you second suggestion, what about creating the single-product
factories as normal, then creating a super-factory:
bind(AppleFactory.class).toProvider(FactoryProvider.newFactory(AppleFactory.class,
GoldenDelicious.class));
bind(OrangeFactory.class).toProvider(FactoryProvider.newFactory(OrangeFactory.class,
Tangerine.class));
bind(FruitFactory.class).toProvider(FactoryProvider.combineFactories(AppleFactory.class,
GoldenDelicious.class));
You'd have to write the FruitFactory interface yourself (although it
just be a combination of component factories). The advantage would be
that we wouldn't need to make any changes to Guice itself.
What's the motivation of this combined approach? Having to only
inject one factory instead of several? Does this come up often?
Jerome
On Jun 17, 1:50 am, Robbie Vanbrabant <robbie.vanbrab...@gmail.com>
wrote:
Let me clarify why I proposed the combined Factory thing: In the end,
this factory generation feature is all about saving you from typing
and maintaining some code. To make sure that your boilerplate code
(configuration, interfaces) doesn't outgrow the original issue, I
thought it would be useful to group related objects, as you would if
you were not using Guice at all. Because if you do have related
objects, chances are high that they need the same kind of factory
assistance, and that they will be used together. In theory -- so hold
your horses. I just thought is was worth discussing.
Robbie