Good Question!
When the service becomes complicated, the number of dependencies will grow more and more
In that case your dependencies are maybe not standalone dependencies but can be grouped into simple dependency-holding classes that can be used to pass into the constructor.
A good sign would be if more than one of your classes use both IRepositoryA
an IRepositoryB
in the constructor and also in some of their methods like in your SomeMethod1
method.
You should be able to find a good name for the class that groups IRepositoryA
and IRepositoryB
. If not, consider going to tell us the real example so we can analyse better.
SomeMethod1() only uses repo A and B, but when client calls it, the service is created with all repositories, meaning it has more dependencies than it needs.
But that is okay because normally you want to make sure that at the time when the class is created you don't want the IRepositories to be changed anymore. They should be declared as immutable. That is a good practice.
If that's not the case and they shall be mutable, then you could make the IRepositoryC
dependency beeing passed into SomeMethod2
as parameter. Currying / partial applying can help you to prevent code-duplication here when you call the method many times with the same IRepositoryC
argument.
A different approach would be passing option-types into the constructor. Your class would then however have the responsibility to check in their methods if a given IRepository
has already been provided or is still empty at the time of the method call.
So it depends a little on what you really want to do. There is imho no general answer to this problem.
Consider these classes, and their dependencies explicitly listed as argumetns to the constructor.
class SomeClass {
public SomeClass(IFoo foo, IBar bar) {}
}
class Foo : IFoo {
public Foo(IBaz baz) {}
}
class Bar : IBar {}
class Baz :IBaz {}
The Graph would look like this:
Now, assembling this by Hand means creating instances the whole object graph, with its dependencies:
var baz = new Baz();
var foo = new Foo(baz);
var bar = new Bar();
var someClass = new SomeClass(foo, bar);
Using an IOC container with constructor injection is not really different. The thing you have to grasp is: You pull on one end, and the graph will follow:
container.add(IFoo, Foo);
container.add(IBar, Bar);
container.add(IBaz, Baz);
container.add(ISomeClass, SomeClass);
// now pull on your "Entry Point"/"Bootstrap":
container.get(ISomeClass);
The container will resolve and inject dependencies as needed. You do not have to resolve anything by hand, with one exception: This is your entry point.
Best Answer
Those two things are essentially the same. A service locator is just any centralized system which lets you find a service to process some request. Using the service locator design pattern, you can use this to get an service implementation(which is really an interface at the code level) from the service locator.
More generally, this system is often used in distributed computing to find a node in the network which can service some other node's request.
For example, some client might want to perform some calculations on some data. Lets say there are multiple ways to do this calculation, each implemented as a service, but the client doesn't care which is actually used. The client could just ask for any service from the service locator, without having to worry which specific type is returned.
In both cases, the benefits and drawbacks are largely the same. The service locator lets you modularize your program better and lets you worry less about specific service implementations. On the other hand, you get this centralized point of failure that may be a weak link in your system. Like everything else, it's a tradeoff so there is no simple "good vs bad" answer.