Just write code. Don't worry about performance unless performance is shown to be a problem.
And to the two you have, let me add a third. Create a base object with all of the desired functions as methods, expecting this
to have your state. Then have your stateful objects set the base object as their prototype
. And now your methods simply exist, and get called, and state is passed around without having another visible parameter.
I am not a JavaScript programmer, and so don't have a strong opinion about which of these is best.
First, I would like to separate the design approach from the concept of frameworks. Dependency injection at its simplest and most fundamental level is simply:
A parent object provides all the dependencies required to the child object.
That's it. Note, that nothing in that requires interfaces, frameworks, any style of injection, etc. To be fair I first learned about this pattern 20 years ago. It is not new.
Due to more than 2 people having confusion over the term parent and child, in the context of dependency injection:
- The parent is the object that instantiates and configures the child object it uses
- The child is the component that is designed to be passively instantiated. I.e. it is designed to use whatever dependencies are provided by the parent, and does not instantiate it's own dependencies.
Dependency injection is a pattern for object composition.
Why interfaces?
Interfaces are a contract. They exist to limit how tightly coupled two objects can be. Not every dependency needs an interface, but they help with writing modular code.
When you add in the concept of unit testing, you may have two conceptual implementations for any given interface: the real object you want to use in your application, and the mocked or stubbed object you use for testing code that depends on the object. That alone can be justification enough for the interface.
Why frameworks?
Essentially initializing and providing dependencies to child objects can be daunting when there are a large number of them. Frameworks provide the following benefits:
- Autowiring dependencies to components
- Configuring the components with settings of some sort
- Automating the boiler plate code so you don't have to see it written in multiple locations.
They also have the following disadvantages:
- The parent object is a "container", and not anything in your code
- It makes testing more complicated if you can't provide the dependencies directly in your test code
- It can slow down initialization as it resolves all the dependencies using reflection and many other tricks
- Runtime debugging can be more difficult, particularly if the container injects a proxy between the interface and the actual component that implements the interface (aspect oriented programming built in to Spring comes to mind). The container is a black box, and they aren't always built with any concept of facilitating the debugging process.
All that said, there are trade-offs. For small projects where there aren't a lot of moving parts, and there's little reason to use a DI framework. However, for more complicated projects where there are certain components already made for you, the framework can be justified.
What about [random article on the Internet]?
What about it? Many times people can get overzealous and add a bunch of restrictions and berate you if you aren't doing things the "one true way". There isn't one true way. See if you can extract anything useful from the article and ignore the stuff you don't agree with.
In short, think for yourself and try things out.
Working with "old heads"
Learn as much as you can. What you will find with a lot of developers that are working into their 70s is that they have learned not to be dogmatic about a lot of things. They have methods that they have worked with for decades that produce correct results.
I've had the privilege of working with a few of these, and they can provide some brutally honest feedback that makes a lot of sense. And where they see value, they add those tools to their repertoire.
Best Answer
Very briefly, it makes program state unpredictable.
To elaborate, imagine you have a couple of objects that both use the same global variable. Assuming you're not using a source of randomness anywhere within either module, then the output of a particular method can be predicted (and therefore tested) if the state of the system is known before you execute the method.
However, if a method in one of the objects triggers a side effect which changes the value of the shared global state, then you no longer know what the starting state is when you execute a method in the other object. You can now no longer predict what output you'll get when you execute the method, and therefore you can't test it.
On an academic level this might not sound all that serious, but being able to unit test code is a major step in the process of proving its correctness (or at least fitness for purpose).
In the real world, this can have some very serious consequences. Suppose you have one class that populates a global data structure, and a different class that consumes the data in that data structure, changing its state or destroying it in the process. If the processor class executes a method before the populator class is done, the result is that the processor class will probably have incomplete data to process, and the data structure the populator class was working on could be corrupted or destroyed. Program behaviour in these circumstances becomes completely unpredictable, and will probably lead to epic lossage.
Further, global state hurts the readability of your code. If your code has an external dependency that isn't explicitly introduced into the code then whoever gets the job of maintaining your code will have to go looking for it to figure out where it came from.
As for what alternatives exist, well it's impossible to have no global state at all, but in practice it is usually possible to restrict global state to a single object that wraps all the others, and which must never be referenced by relying on the scoping rules of the language you're using. If a particular object needs a particular state, then it should explicitly ask for it by having it passed as an argument to its constructor or by a setter method. This is known as Dependency Injection.
It may seem silly to pass in a piece of state that you can already access due to the scoping rules of whatever language you're using, but the advantages are enormous. Now if someone looks at the code in isolation, it's clear what state it needs and where it's coming from. It also has huge benefits regarding the flexibility of your code module and therefore the opportunities for reusing it in different contexts. If the state is passed in and changes to the state are local to the code block, then you can pass in any state you like (if it's the correct data type) and have your code process it. Code written in this style tends to have the appearance of a collection of loosely associated components that can easily be interchanged. The code of a module shouldn't care where state comes from, just how to process it. If you pass state into a code block then that code block can exist in isolation, that isn't the case if you rely on global state.
There are plenty of other reasons why passing state around is vastly superior to relying on global state. This answer is by no means comprehensive. You could probably write an entire book on why global state is bad.