5

I am using Spring4d framework for dependency injection and other things.

In the application entry point, I have to create the application "Main" form. Though, I do not know of any other way than

Application.CreateForm(TMainForm, MainForm) 

to create this.

Is it possible to create the Main form using Spring4d dependency injection ? Like so :

MainForm := GlobalContainer.Resolve<IMainForm>;

and then set it to be the form that will be shown when I open the application?

Ludovic C
  • 2,855
  • 20
  • 40
  • 1
    Ultimately something has to call `Application.CreateForm(...)` for the main form. Otherwise the VCL framework won't know that there's a main form. Are you really doing DI for the main form? Is that really a good idea? – David Heffernan Dec 08 '15 at 20:03
  • I thought it was until you asked... I think this is a good idea because the main view may have a couple of dependencies that would have to be resolved. Those could be view factories (TFunc or lazy views TLazy ). And doing DI on this would ease the testing process. – Ludovic C Dec 08 '15 at 20:10
  • Why is it a bad idea ? – Ludovic C Dec 08 '15 at 20:27
  • 1
    It seems odd to me to be doing DI on the main form of the app. Why would you the dependencies mean you needed to use DI to create the main form? – David Heffernan Dec 08 '15 at 20:31
  • I'm not sure I understand your question, could you reformulate please? – Ludovic C Dec 08 '15 at 20:36
  • 1
    Are you planning on having multiple implementations of `IMainForm`? Anyway, don't you just register a type, `IMainForm`, and delegate it to a function that calls `Application.CreateForm`? Leaving aside whether or not you need to do this, it's eacy enough to arrange that `Application.CreateForm` is called. – David Heffernan Dec 08 '15 at 20:39
  • 1
    Oh, it's not about having multiple implementations of IMainForm. It's simply because the MainForm will need factory functions that are easier resovled using Spring4d that having to pass them manually. Moreover, I may want to test that Main form by passing mocks of those factory functions / dependencies. It's not about having multiple implementations. And yes, as you say, I think it's a good idea to register a delegate constructor that calls Application.CreateForm. I just wanrted to know if there was a way around it. – Ludovic C Dec 08 '15 at 20:41
  • 1
    I don't see why you need to create the main form using DI for it be able to resolve those dependencies. But I know little of DI. – David Heffernan Dec 08 '15 at 20:45
  • With spring4d, you just specify the dependencies as interfaces in your constructor like : TMyForm = class(TForm, IMyForm) TMyForm.Create(aDep1 : IDep1; aDep2 : TFunc) And when you call Container.Resolve, then the aDep1 and aDep2 dependencies will be passed to the constructor. Passing IDep1 will resolve as an instance of IDep1, and TFunc as a factory function for IDep2, and TLazy as lazily initialized instance of IDep3. It's pretty neat. There's a lot of other stuff I did not understand yet but it's very nice. It also resolves recursively. – Ludovic C Dec 08 '15 at 20:56
  • 3
    This seems like cargo-cult DI to me. Not a good idea. – Warren P Dec 08 '15 at 21:17
  • 1
    Well you're just plain wrong if you think this DI is useless. If you haven't heard about Spring4d and you're a delphi programmer than I suggest you seriously look it up. It's a Delphi port of Spring, the almost most widely used framework in the whole world. John Frum himself programmed it. – Ludovic C Dec 08 '15 at 21:21
  • Thanks for the downvote mate. – Ludovic C Dec 08 '15 at 21:24
  • 3
    You have to know when something should be used, and when something shouldn't. TApplication Needs you to call CreateForm. That's a rule. You can't break it. You could have the main form of your application be empty and compose all its contents dynamically with spring. But you can't modify the VCL. – Warren P Dec 08 '15 at 21:27
  • Well just look at that https://bitbucket.org/sglienke/dsharp/src/ad7c5983505f0117f1347f92d2bb96c07bdfda94/Samples/MVVM/Calculator/Calculator.dpr?at=master&fileviewer=file-view-default – Ludovic C Dec 08 '15 at 21:29
  • He's probably calling CreateForm somewhere, but still, he's resolving his dependencies with a container. – Ludovic C Dec 08 '15 at 21:30
  • 1
    Yeah, so what's the use of that? You just made your code impossible to debug. – Warren P Dec 08 '15 at 21:30
  • Not at all? What are you talking about? It's about resolving your dependencies dynamically. It's a DI container. It's not Saruman's Magical wand. – Ludovic C Dec 08 '15 at 21:32
  • 1
    @Ludo Warren didn't say DI was useless. He's just suggesting that you may be overusing it. As he says, use the right tools for the job. And they aren't always the same tools. – David Heffernan Dec 08 '15 at 21:34
  • I can understand that, but in this case, I don't understand **why** that would be a bad tool to inject the main form as another dependency. – Ludovic C Dec 08 '15 at 21:37
  • 1
    If that was built into TApplication, it would be a good idea. If TApplication was your code, it would be a good idea. It's neither of those things. It's a bad idea. – Warren P Dec 08 '15 at 21:37
  • 7
    If you are not going to have multiple implementations of `IMainForm`, I can't see the point of adding more indirection. – David Heffernan Dec 08 '15 at 21:41
  • 4
    In any battle between KISS, YAGNI, and DI, let KISS and YAGNI win. – Warren P Dec 08 '15 at 21:59
  • I'd like @sglienke advice on this – Ludovic C Dec 08 '15 at 22:24
  • Great question - I am sad that people down vote it just because they don't understand it or have a different opinion. – Stefan Glienke Dec 08 '15 at 23:27
  • 1
    I could even understand why you would use `IMainForm` and possibly even something as `IApplication`, I did that in one test project that had VCL, FMX and Console front-ends but in an application root the `IMainForm` was resolved as `IApplication` (something that does the `.Run` call) dependency. (Ie. an application that has the same business logic and the only difference between front-ends are the views and portion of registration code). – Honza R Dec 09 '15 at 07:38
  • @StefanGlienke so you're saying that people shouldn't be able to express their opinions, unless they're the same as yours? – penarthur66 Dec 09 '15 at 11:51
  • @penarthur66 No, I am saying that questions should not be downvoted because of that - check http://stackoverflow.com/help/privileges/vote-down – Stefan Glienke Dec 09 '15 at 11:57
  • @StefanGlienke You're right. But to be fair I expect people think it like more normal newspaper site "do you agree" downvotes, rather than purely based on the quality of the question. – penarthur66 Dec 09 '15 at 12:46
  • I wouldn't think this question was going to raise such a debate. But I think it offended some people, I got 2-3 downvotes on this questions, and simultaneously other downvotes on my other older Delphi questions. – Ludovic C Dec 09 '15 at 13:33
  • I didn't downvote other questions. But this question seems to me "how can I use DI when it has no value, and indeed, has some terrible cost in readability and straightforwardness". – Warren P Dec 09 '15 at 15:29
  • 1
    Imho, I think DI simplifies many things, enforces the KISS principle, and the Single Responsibility principle. When injecting your Composition Root (your MainForm), you are resolving **all** your application dependencies. You almost never have to call Create ( http://misko.hevery.com/2008/09/30/to-new-or-not-to-new/ ) NOR Container.Resolve(). Also, Application.CreateForm(TMyForm, Form) is quite "terribly" equivalent in readability AND straightforwardness as Form := GlobalContainer.Resolve. So, imho, you're plain wrong saying it has no value AND has terrible costs. – Ludovic C Dec 09 '15 at 15:52
  • I think when you say "costs in readibility", it really means that you are either not used to composing your object graphs, or that you don't understand the mechanics. I mean, what other purpose would your DI container serve than by resolving your composition root (your MainForm) ? – Ludovic C Dec 09 '15 at 15:59
  • @StefanGlienke I don't think questions should be voted down because people don't understand the question. But I don't see how anyone can discern why a question was voted down. – David Heffernan Dec 09 '15 at 17:13
  • I think the downvoter just did not agree / understand with the principles of DI / IoC. Like someone who downvotes a Java question because it's about Java. "What good is Java for anyways?" – Ludovic C Dec 09 '15 at 17:34

1 Answers1

10

When you register your main form with the DI container you can specify the factory function to create the instance by passing it to the DelegateTo method.

In my opinion there is no need to resolve the main form as interface because it is the composition root and it will not be passed anywhere else so I will register it like following.

container.RegisterType<TMainForm,TMainForm>.DelegateTo(
  function: TMainForm
  begin
    Application.CreateForm(TMainForm, Result);
  end);

And then you can just resolve it calling

container.Resolve<TMainForm>;

However the benefit of letting the container resolve the form is that it may inject dependencies into it which will not happen here since the code inside of CreateForm creates the instance. That is where the possibility to call additional methods via container after construction comes into play. So instead of passing dependency into the constructor as usual you can add a lets say Init method to the form class that takes the dependencies it needs and add the [Inject] attribute to it. That will tell the container to call this method after the instance was created (in our case through the factory function passed to the DelegateTo method) and pass all required dependencies to it.

A minimal empty main form that can take dependencies via container would look like this:

TMainForm = class(TForm)
public
  [Inject]
  procedure Init(...);
end;
Stefan Glienke
  • 20,860
  • 2
  • 48
  • 102
  • 2
    I can't see any benefit over call Application.CreateForm in the dpr file. Presumably there are some. What have I missed? – David Heffernan Dec 09 '15 at 06:54
  • 1
    If you just call `Application.CreateForm` then the container doesn't come into play so no DI can happen (you would have to call `MainForm.Init(container.Resolve, container.Resolve, ...)` yourself). Also the container doesn't register `MainForm` instance into itself so you cannot inject it to classes that depend on it. – Honza R Dec 09 '15 at 07:27
  • Thank you Stefan, that was exactly what I was looking for. – Ludovic C Dec 09 '15 at 13:23
  • Stefan, it seems like when using the DelegateTo method, there is no field injection happening. – Ludovic C Dec 09 '15 at 13:51
  • Wow. I did not find this until now. Pretty neat using a helper class http://stackoverflow.com/questions/24284401/how-to-initialize-main-application-form-in-spring4d-globalcontainer – Ludovic C Dec 09 '15 at 14:06
  • @Stefan Glienke Check this small project on GitHub, there is no dependency injection going on at all. Am I doing something wrong ? https://github.com/ludydoo/Spring4d-NoInjection – Ludovic C Dec 09 '15 at 14:26
  • You saw the two W1025 in your compiler messages? – Stefan Glienke Dec 09 '15 at 14:32
  • No, just AV cause the injection did not happen – Ludovic C Dec 09 '15 at 14:34
  • Oh, no that's it two W1025 – Ludovic C Dec 09 '15 at 14:35
  • Just needed to add Spring.Container.Common to uses... Feeling dumb. – Ludovic C Dec 09 '15 at 14:41
  • I hope I never work anywhere on any code you built. :-) You may come to regret an excess of Springy-ness in your code in years to come. – Warren P Dec 09 '15 at 15:27
  • 2
    @WarrenP No need for borderline insults. If you have complaints about this approach you are welcome to discuss this with us on our forums (https://groups.google.com/forum/#!forum/spring4d) - also just because many people with a lack of knowledge of DI jump right into using the container that is not a fault of the library. I always tell people to write their code pure DI compatible and thus avoid things like field injections or similar. – Stefan Glienke Dec 09 '15 at 15:39
  • @WarrenP If you're going to work on that code someday maybe you'll start to like it too much :) – Ludovic C Dec 09 '15 at 17:01
  • Why is it better for the fields to be injected from the outside, rather than have the type ask them to be resolved? And why is it better for `Init` to be called by the framework instead of the form's constructor doing the work? So, why can't the form type have a constructor that asks the framework to resolve all of its dependencies? Note, I am asking because I would like to know the answer. I am not casting any judgement. – David Heffernan Dec 09 '15 at 17:15
  • If dependencies are resolved by the Class that uses them, you're coupling yourself to a single implementation of that dependency. Not testable. It's coupled. And about Init, I guess it's because the VCL requires Application.CreateForm. If you have dependencies that must be resolved, you must inject them somehow. Init just bypasses the CreateForm limitation. And if you have dependencies to the DI container all around the place, it's as ugly as not using DI at all. Just specify your dependencies in the constructor, and you're good to go. The constructor just assigns the deps : fDep1 := aDep1 – Ludovic C Dec 09 '15 at 17:28
  • I understood David's question was about DI in general, so I answered "In general". About the main form, I think you still did not understood that the library simply resolved the dependencies for that form. Nothing spectacular. Just useful. – Ludovic C Dec 09 '15 at 17:35
  • @Ludo That's not what I mean. I wonder why the constructor of the form can't just ask the DI framework to resolve dependencies. That's just as decoupled. So instead of pushing them in through a field injection, or your `Init` method, have the constructor ask the DI framework to resolve them. I cannot see the difference between the constructor doing what is in `Init`, and the framework calling it. In fact the only difference is that the constructor can do it during construction, rather than it having to wait until after. – David Heffernan Dec 09 '15 at 17:45
  • You are talking strictly about the MainForm, or every Form ? If you are talking strictly about the MainForm, I see no difference myself between calling the Resolve or by calling the DI framework to create the deps, but maybe the shorter syntax for the former. – Ludovic C Dec 09 '15 at 17:46
  • @DavidHeffernan Check here https://github.com/ludydoo/Spring4d-NoInjection I've added two projects with both syntax. – Ludovic C Dec 09 '15 at 18:05
  • It just seems to me to be cleaner for an object to resolve its dependencies in its constructor by asking the container – David Heffernan Dec 09 '15 at 18:16
  • Then you are coupled with the DI framework in all your units – Ludovic C Dec 09 '15 at 18:25
  • 2
    Before, I also thought that the Container should be used in any unit to resolve the dependencies. That seemed like the right way to do. But it's event cleaner when you don't have any dependency even on the DI framework. But, really, it's only in the composition root that the container should be used. Anywhere else, you don't have to worry about the dependencies. They're just there for you to use... – Ludovic C Dec 09 '15 at 18:36
  • 1
    There's one way you can use the container to resolve the deps inside the code, and that's the Service locator pattern. It's used to progressively refactor legacy code to use DI. But if you really follow DI principles, you won't need it in the end. – Ludovic C Dec 09 '15 at 18:37
  • @DavidHeffernan What do you exactly mean by "It just seems to me to be cleaner for an object to resolve its dependencies in its constructor by asking the container". Are you suggesting a) to put these dependencies as argument on the ctor or are you b) suggesting to resolve them inside the ctor from the container? If a) because of VCL `TApplication.CreateForm` in the case of the main form which is an exception - you can do that just fine for any other form class that is not created using this VCL mechanism. If b) because that the service locator pattern - it could be used in this special case. – Stefan Glienke Dec 09 '15 at 18:49
  • @DavidHeffernan Check the updated sample at https://github.com/ludydoo/Spring4d-NoInjection/blob/master/Logger.pas .Check how the logger constructor automatically get its dependency. – Ludovic C Dec 09 '15 at 19:02
  • @Stefan Thanks, I think I get it now – David Heffernan Dec 09 '15 at 19:56