-
Notifications
You must be signed in to change notification settings - Fork 89
instanciate non-mapped classes #36
Comments
This will be supported in a slightly different fashion in Swiftsuspenders 2. Instead of just instantiating unmapped classes, it will be possible to set a default dependency provider that will be invoked after no specific provider has been found for a dependency. This provider can be used to simply create an instance of the requested class. |
How is this looking? |
Right, the default provider. I remember having some problem with it, but I'll look into that once again. |
Am I correct that at the moment one can do this:
and a new instance will simply be created. Whereas, if a container-constructed class has a dependency on such a non-mapped class an error will be thrown? Symmetry! |
Gah! Yes, you are correct. And yes: Symmetry! Maybe this even ties in nicely with my strongly held belief that interfaces should only ever be created if there are more than one implementation of something. Changing the injector to just inject new instances of a requested class if they're not mapped would mean that a mapping only ever needs to be created if an interface is requested, too - which seems nice. On the other hand, I remember you wanting singleton mappings to be the default (still not gonna happen!), indicating that for a large number of use cases, the mapping would still be required. |
@darscan recently made a pretty good point about there being a nice symmetry between Injector#getInstance() returning new instances for unmapped types and the injector just instantiating unmapped classes when requested dependencies. Hence, this is how it works, now. |
Sorry guys, I missed this. "I forgot to map this" is a mistake I make frequently. In RL1 that's cool, the injector has my back. But Silent Fails are a nightmare to debug. I suspect the symmetry isn't real - the process you're entering into is different. Actually, I think it should be the other way around. These processes should be less symmetrical. If anything I think how it was before was a feature, not a bug. "Make this according to the rules I have given you" is different from "Make this". Initially I thought that as I like interfaces I wouldn't see these silent fails anyway, but actually this is a big issue around the signal command map - if the command expects a value to be injected and it isn't passed in the signal then that's unlikely to be something that you don't want to be told about. I know folk have complained that they can't pass null in a Signal before, but I think that's a feature not a problem, and we could have a generic NullInjectableObject that the injector doesn't choke on for those situations where folk want to do it deliberately and can't be bothered to implement their own NullObjects [conditionals be damned but hey ho]. So, I'd prefer the injector to protect me when I've forgotten to map than be all-forgiving of missing mappings for concrete classes. If I just want to use the injector as a factory, newing up stuff as it needs to to get the job done, and not holding on to those values, there could be an explicit method for that...
However - the original poster's suggestion, that this could be a flag on the injector, sounds workable to me as well (without considering how Till implements it). At least it lets me opt out of the silent fails, and we could specify that this mode is not compatible with use of signal/message-passing-object based commands. It's also not advisable to have this flexibility when using with guards/hooks on the mediator map, where the concrete view injection might just quietly new up a view in your hook rather than help you diagnose a problem. I'm thinking of when people inject a view into a guard or hook against a (super or sub) type that they didn't specify in the TypeMatcher. I was relying on the injector to help them diagnose this problem. On the interfaces issue: it's absolutely not that I don't believe the same as you Till - I believe you're exactly correct about the preferred situation in theory, but that without duck-typed interfaces it's not deliverable in AS3, unless we give up the nice DSLs and are prepared to make modular compilation much more cumbersome. Personally, I think the DSL alone is worth the hit of having to have interfaces, even without considering how much more difficult modular development becomes without them. |
So I'm mostly convinced - thanks for the clear arguments against auto-instantiation, @Stray. I'll leave this open and wait for other comments for a bit, but if nothing new comes up, I'll probably implement the flag-based solution. Both because it's easiest and because it seems closest to satisfying demand while preventing problems. @Stray, so as not to smear that discussion all over the place, could you add yet another explanation of why class-interface dualism is required for the base case in DSLs to the mailing list discussion? Sorry for being so dense, but I honestly still don't get that point. (I get, but don't agree with, the point about increased difficulty of modular development.) |
Regarding this question I would suggest to follow The zen of python: Explicit is better than implicit. (witch I did in my mvc implementation and it works like a charm.) just my two cents... |
What's the one obvious way to do it? And what exactly is it in this case? |
proxyMap.map(new MyProxy(), IMyOptionalProxyInterface, "optionalName"); If I need a proxy... I just map it. If I need it only sometimes... I have commands to handle that. |
I'm not sure I can follow. What are "proxy classes" in your framework |
Hm... Good point. In both cases injection is used... but we use different approaches. Your approach is universal and generic, mine is tied to framework and does what I need and only what I need.(to make it run fast and avoid bloated implementation..) Ignore me. |
Can you clarify why you decide to do away with I feel like constructor injection is orthogonal to mapping... and yet the current situation is that I can make a new instance and injectInto it, which only locks me out of constructor injection. That feels... spurious, but perhaps there's a damn good reason? |
Extra thought: the current situation also locks me out of [PostConstruct]. (Unless InjectInto also runs PostConstruct methods? But that seems confusing...) At first I thought just an |
I'm not sure I understand what you're getting at.
That is, it does exactly what |
Agreed... but I think we've already identified that it shouldn't? Because we need the errors (unless the user deliberately switches them off). |
Ah, now I understand what you're getting at. This is a different topic For some discussion that lead up to the removal of Basically, the thinking is that As Also, the amount of confusion about |
"As Except in the extensions that rely on it... though if we do proper testing on those we won't be affected. I hadn't fully appreciated the getInstance manual / automatic differences. Still... that feels like possibly more of a case for The thing is, it seems to me to be perfectly valid (particularly within an extension setting) that you might want a new instance, regardless of any that is mapped as a Singleton. Are those two things necessarily related? But... now I shall go and read those issues 15 and 25 and get myself up to speed on what I've missed (as I'm sure there are good reasons). |
Ah... I may be being stupid, could one simply get a childInjector, make a new mapping and getInstance on that childInjector if you wanted a fresh instance? |
That is certainly possible, yes. Or you could use |
Yes please! I actually think there are 4 distinct behaviours here and we're trying to cram them into one or two API functions.
For me, those fall on to:
For 3 and 4, the other option, already supported, is to use a custom default dependency provider, but that then requires you (in the case of having both 3 and 4) to track which of those is which in a second location in your code, which is a bit icky. I think this would cover all the concerns folk had across issues 15 and 25 (though I expect my reading suffered a little from bias ;) ). |
For clarity - I've gone for |
Have to think about the others some more, but just as a quick |
4 would be the solution to the OP's problem - where he has a nested set of dependencies (think army of Robots each needs 2 legs, 2 arms, legs need knees, arms need elbows etc) and doesn't want to have to map(RobotToe).toType(RobotToe) for each body part. At this point you're just using the injector as a smart factory. Whether that's a valid use case I'm not sure, but it seemed to be something people wanted. If you do 4 with interface dependencies then Computer Says No. (Noisy but helpful error). |
Thanks for the explanation/ reminder. Will have to mull all of this over some. |
Yes. History shows the discussion has rumbled on for 3 years - I'm guessing an afternoon isn't going to be sufficient :) |
IMHO I think most use cases could be covered (whilst keeping things sane, and hopefully easy to implement) by two things: 1 - A flag on the injector to toggle how unmapped classes are treated. Naming is difficult here. I've been mulling over 2 - A method that allows one to simply new up instances (as per the old I was unhappy about the removal of I think what I'm getting at is that |
Damnit, that doesn't quite cover the needs of the SignalCommandMap though, were we need the ability to instantiate the Command class (unmapped) but explode if the command specifies any unmapped dependencies... |
Yeah, that was my original "Well, this ain't gonna work!" example. I think the problem with the flag is similar - that moment-to-moment I may vary what I want the injector to do with an unmapped dependency. It seems saner (to me) to be explicitly stating that as you make the demand of the injector, not having it hidden elsewhere and set global-ish. I say global-ish because we haven't talked about the child/parent injector's response to setting the flag on one specific injector. If it doesn't trickle up, and I want to set it globally, do I need to do injector.rootInjector.... ? Hrm. Nightmare scenario: someone else decides to set the flag on the injector somewhere that may even be in a different module (if it can be set / unset at any time) and depending on order of execution, injections sometimes error and other times are fulfilled (and even then, sometimes fail silently, because you've also forgotten to make the mapping anyway). I guess the same is true of unmapping at runtime, but I think that would be easier to diagnose - in that one could more easily search for unmap(Thing) in the codebase and figure out how to unknot that problem. If the flag is locked at startup, that seems insufficiently flexible. On 14 Jul 2012, at 17:37, Shaun Smith wrote:
|
That's a slightly separate issue to the original issue in this thread (although all this stuff is related, so it's tricky). Some people, myself included, would like reduce the boilerplate of simple class mappings, and instead let the injector do the work. But that's at the "application" dependency mapping level - I still want the extensions etc to explode when things are incorrectly configured. I consider the context injector mine, but all extensions should use their own (child) injectors, where unmapped dependencies should explode. @tschneidereit I know you are sold on the fallback dependency provider pumping out new instances willy-nilly for every request (you insane man you), but is that the sort of thing I could change myself by providing a reasonable (singleton) dependency provider? p.s. interleave lots of ;-) 's into that question! |
@Stray Sorry, I didn't explain why it's a separate issue. Essentially it's to do with this part:
This relates to dependency resolution within the application, which happens outside of your control a lot of the time, so there is no place where you "make the demand". If that makes sense at all.. sorry, I don't think I'm being very clear. |
Ah - yes. I see - I think that cleared it up for me. So actually there's the 4 specific scenarios I outlined when 'pulling' from the injector, plus the desire to toggle the default 'push' behaviour. I'm inclined towards:
On 14 Jul 2012, at 18:14, Shaun Smith wrote:
|
So - I done forked it and implemented this, as a starting point. Deets here: https://github.com/Stray/SwiftSuspenders/blob/fallbackProviders/fallbackProviders.md |
I would like to see an flag on the injector to force it to instanciate non-mapped classes to avoid long lists of simple classes mapping. So, we could do:
injector.instanciateNonMappedClasses = true;
insted of:
injector.mapClass(ClassA, ClassA);
injector.mapClass(ClassB, ClassB);
injector.mapClass(ClassC, ClassC);
...
The text was updated successfully, but these errors were encountered: