To unsubscribe from this group, send email to google-guice+unsubscribe@googlegroups.com.
--
You received this message because you are subscribed to the Google Groups "google-guice" group.
To post to this group, send email to google...@googlegroups.com.
To unsubscribe from this group, send email to google-guice+unsubscribe@googlegroups.com.
Inline:
On 7 Nov 2012, at 16:53, cowwoc wrote:
Hi Christian,
Let's work through these one by one.
- We lose the ability to static analysis the code to determine if the wiring is correct. o I've never heard of using static analysis to checking wiring. Can you please point me to an example?
Guice doesn't do it now, but Dagger does, and Guice ultimately will, if you limit your use to patterns that are not inherently hard (computationally) to analyze.
- We can't see dependencies from method signatures. o True, but I argue that users shouldn't be aware of these dependencies.
This is complex… it depends on the kind of object this is. Typically objects you would want to create from factories based on data you have in your local stack frame already (i.e., locals, parameters, etc.) aren't ones that you want to hide the parameters of.
You're using the word dependency too broadly… what KIND of dependencies are you talking about. In some concrete cases I might agree with you. In others I would not.
o We're asking users to pass in what amount to pseudo-constants.
Imagine if we used inversion of control to pass in Math.PI.
Would users be happy to know that a class depends on the value
of PI? Who cares? :)
Can you please pick a non-absurd example of this? I would not use dependency injection at all to supply Math.PI.
they're not pseudo-constants… they're scoped. Math.PI is global and stateless data. That's quite different than a service object. It's also different than a piece of context that can't be a constant, because it's not universal (say, a "current user"), but is effectively like a constant for that object's perspective. I don't mind injecting these, in carefully controlled circumstances. But usually I find that the benefits don't outweigh the costs on code clarity. This is subjective.
o Typically, injected types are bound once and users never
see/hear about it ever again. Often, inject values come from
external frameworks (e.g. Jersey). This stuff "just works" 99%
of the time.
I'm not sure how this is relevant to service vs. factory method. IF you don't want the calling clients to see it, don't include it in the service interface, and just inject it normally. You don't have to change a factory method signature for non-assisted constructor parameter changes.
o I argue users should only see parameters that are most likely to
concern them. When I construct a type where some parameters are
injected and some are supplied by the user, I only never pay
attention to the injected types. Should I?
assisted-inject lets you mix this… actually, the end-clients should only pay attention to the "assisted" parameters. The other injected dependencies are entirely invisible to calling code, as it's injected by guice.
- This approach makes it harder to maintain dependency lists. o I suppose you could generate dependency lists from your Module(s) or invoke the Guice SPIs.
Possibly - I'd need to see this in practice to decide if it's worth the payoff. My instinct says no, but I have things I'm used-to.
o Though, I argue that the entire point of Guice is to hide such
dependency lists from us.
Not all dependency lists, and not in all places at all times. Again - you're using the one word dependency to refer to many kinds of things that have different purposes, uses, structures, and life cycles. It's important to define these kinds of things in your code to determine if injecting them automatically, manually, or not at all is appropriate.
o If we really cared to see them in their glory, we'd pass all
transitive dependencies into the top-level class. That way it
would be clear that we really depend on all of these types.
Except it doesn't depend on these types in any way that it cares about. It implicitly does, but the point of Guice-style DI is to separate explicit from implicit dependencies. If you did what you describe, you force all classes to depend on all classes, and altering behavior elsewhere in the system would require changing it in all places, rather than only behind the wall of encapsulations.
o Instead, Guice hides transitive dependencies from us for the
sake of clarity (as it should).
And other benefits to this hiding, but yes, agreed.
- Place AssistedInject factories near the constructor. o I agree this helps but like you said it ends up cluttering the file so I also ended up moving it to the bottom.
If it's just an interface, I don't think it would clutter the file much. If it's a class with method bodies, it would. I guess I just treat it as two places I have to look. Sadly, there isn't (yet) a refactoring plugin that can instruct IDEA/Eclipse to be aware of @Assisted properties and their factory companions, so you can change it once. I still maintain that this is a small cost for better cognitive result in the resulting code.
o And remember, the module/bindings still reside in a separate
file, so you're still going to have a harder time keeping the
two synchronized.
No - if you're re-organizing the dependencies, but no new things are bound, then you don't have to change the bindings. The bindings aren't the dependency graph, they're the manifest of what participates in the system. If the types are all registered (@Provides or bind().to()) then you're all set.
- You brought up an interesting point about being able to inject different kids of injected things (e.g. collaborating services, etc) but I didn't really understand what you meant. Can you please provide a more concrete example?
Sure. These are a bit cerebral, but are quite familiar in most web apps in most companies.
Services: - global and expensive (often), providing facilities for other classes
Factories: - a kind of service that creates types that are frequently created.
Configuration/Context: A value type or complex object that is used to provide context for services and other logic, but is (from its dependents' perspective) largely immutable. A pseudo-constant, as you call it.
Value Type - Data. Primitive types, primitive holders/wrappers, complex objects which represent the data of a program, collections of such, etc.
Assisted Inject is highly useful when you have composite value types - complex objects that need some data in their creation - data that makes them unique (A User's userId) - and some services they rely on to do their job, say, a LoggingService, or some other internals.
Whether something fits these categories depends on their usage. Even something like a User can fit multiple roles in different contexts. So it's not hard and fast. But things like assisted inject give you the opportunity to start segregating these roles, and seeing how things are used based on their patterns of use is a helpful signal in understanding the code flow.
This is, as an aside, why I'm often uncomfortable with people writing too much code that injects value types as context. It can be powerful, but it can also confuse things, especially if that value type is context here, but data over there. It can lead people to need to create artificial scopes in Guice just to handle the fact that they structured their code to treat a value type as context, always.
Hope that helps.
Chirstian.