Closed erenkabakci closed 3 years ago
So you start out by discussing interface injection, then show an example of annotation using @Injected.
Regardless, if you want to actually user interface injection...
class ClassC {
lazy var someProtocol = getSomeProtocol()
...
}
extension ClassC: Resolving {
func getSomeProtocol() -> SomeProtocol { return resolver.resolve(ClassB.self) }
}
Where you explicitly tell Resolver which class you actually want to use for the instance in question.
You can do the same in constructor injection...
register { ClassC(someProtocol: resolve(ClassB.self) }
Bottom line is that Resolver isn't magic. It does type inference, but if you have more than one instance of something you have to differentiate one from the other. Above we do it by explicitly defining the class type.
Another very common solution to the problem is to use namespaces.
The very first example on that page illustrates just the situation you describe, where we want to specify just which provider of the XYZServiceProtocol we want to use at that point in time.
Thank you for the quick response!
So you start out by discussing interface injection, then show an example of annotation using @injected.
Well, "annotations" are indeed only annotations 😅 Does it matter if you annotate a concrete type or an interface? I don't think so.
Anyhow, for the first example you gave, ClassB
has to be defined within the same framework/module/namespace in order to work.
Imagine that ClassC
implementation and the SomeProtocol
declaration is defined in a low level framework e.g. networking. But the actual concrete class injection and the dependency creation happens on the top level. e.g. app target which imports the networking in this case. So in this case classC should define another method in the protocol signature so it can be fulfilled by the top level injector level, as far as I understand. Because ClassC and the framework/target/module has no idea about ClassB
and shouldn't 🤷
Now looking at the namespace case you pointed out, that's indeed can be a way to tackle this. I should look into that one. Thanks!
I was perhaps being pedantic, but if you follow the first link I gave you to the documentation on Interface Injection you'd see that the term has a specific meaning. An "interface" is injected into an object that allows it to consume an injected service.
The first example I gave was an example of true interface injection.
But I think named interfaces are indeed the solution to the problem at hand.
As documentation suggests, interface injection is supported out of the box and frankly should be the default way (personal opinion). Since injecting abstractions via protocols should be preferred instead of concrete classes (for testability and composition purposes), using Resolver for such case works but only partially. Here is an example below.
As seen on the simple example above, different types can conform to the same protocol, yet there is no distinct way to point which of the instances in the implementation. In poor man's dependency injection via constructors, we can always provide the specific concrete instance but the other class doesn't know anything about it since it only cares about the protocol conformance. This way, we can still keep a reference to the same instance and provide the only necessary contract via the protocol.
On the example above, resolver creates a brand new
ClassB
instance and injects it toClassC
, but in reality what if I want to reuse my sharedClassA
instance?As we know this is a very common way to practice DI while preserving testability and limiting exposure. I am wondering if there is a way to satisfy this via Resolver, if not, how can people use it? Multiple conformances in different types are very common in modern coding.