3

I'm working on a system where we consistently apply "patterns" or approaches such as Dependency Injection.

I'd rather not expose the current underlying technology as not to get biased solutions, because I think my question would apply to many different languages and runtimes. It suffices to say that I'm using an Object Oriented platform, where I have classes, delegates, and all shenanigans expected on a modern language space.

Throughout development, I followed the following pattern:

  • I have a class called ConfigurationProvider which has a simple method get where you can pass him sectionNames, by which one can retrieve configuration in a very decoupled way. The configuration returned by get is actually a hash structure, form which the caller can then pass configuration keys and retrieve configuration values.

  • Most high-level services that I have expect to receive a instance of this object in order to access configuration. This way, they are able to configure themselves. This give me a certain flexibility to move configuration around (the final structure provided to each object must, of course, be respected) without affecting the caller object.

Let's give an example:

ConfigurationProvider config = new ConfigurationProvider(); Cache cache = new Cache(config); UserProfileService userProfileSvc = new UserProfileService(cache); 

As you can see, all cache details such as host and port, default time-to-live, etc., are completely abstracted. Users must then follow the rules of cache configuration. For instance, my application could have a configuration file called config.json:

{ "cache": { "host": "localhost", "port": 5555, "logErrors": true } } 

However, a problem arises (or should I call it trade-off?). The object is now coupled to the ConfigurationProvider. That can be largely solved by reimplementing its interface, so that you could pass any configuration you'd like as an object, but it's a lot of hassle. Of course, I can (and certainly should) create a constructor overload that receives the configuration directly instead of the provider.

While it may seems that I've answered any question I might have, my question still is the, is there a pattern, or at least convention, on this matter? Is this an anti-pattern?

1
  • Adding an interface implemented by one class does not decouple anything other than the constructor for that class, which can be hidden in other ways. Such an interface only provides value when the interface and the implementing class must be in separate assemblies, and there are many implementations of the interface, and even there, a base class can serve the same purpose, with multiple inheritance limitations.CommentedAug 23, 2018 at 16:16

1 Answer 1

2

Congratulations, you've reinvented the service locator pattern. You've correctly identified it's main drawback: it couples you to the provider.

You're main alternative to this is dependency injection. Not of the container but of the dependencies.

If your object knows how to use a container it's not decoupled from it.

What I would recommend is to only let you objects know about things they need. That way they don't end up coupled to how to find them.

This respects the law of Demeter but it can be taken to far.

Their is a refactoring called introduce parameter object. It's similar in that it minimizes the number of parameters in your constructors but it's better than service locator because the parameter object is customized to how it is used. It's not a one size fits all that everything has to learn to deal with. It's only used in a few places. It works well if you give it a good name.

You can also create factory methods that take no parameters, build the needed dependencies, and inject them into constructors leaving the object clueless about where it's dependencies came from. You can have many factories that build this object using different dependencies. It's easy so long as your good at thinking up factory names.

Lastly there is the good old build it all in main() pattern. Construct dependency after dependency. Give them all names. Pass them to what needs them. When everything is built and connected then start the whole thing ticking by calling process.start(). Done that way the only thing coupled to your construction code is main() itself. The drawback? You have to think of good names.

There are patterns that avoid the need to give things names but while they make the CPU happy they rarely make the humans happy.

2
  • Not sure that what I'm doing is a service locator - I do inject the ConfigurationProvider instance, and that isn't a service locator, because all it is is a Key/Value container, which enables objects to retrieve their configurations. A service locator, by definition, produces services, not strings.CommentedAug 21, 2018 at 16:39
  • (continuing...) It seems though that what you're telling me is that only the bootstrapper should know where the configuration comes from. Which kind of makes sense... Is it? Can you cite references in frameworks?CommentedAug 21, 2018 at 16:43

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.