The problem of coupling

The phrase tight coupling in programming is referred to situations where two entities are so highly dependent on each other that in order to change either of their behavior or relationship, it is required to make actual code changes to one (or often both) of those entities. The term loose coupling, on the other hand, refers to the opposite scenario--entities that are not highly dependent. In such cases, entities ideally don't even know about each other's existence, but can still be made to interact with each other.

With that in mind, what would you say the coupling of two modules in the Java module system could be called? When one module depends on another, are the two modules tightly coupled or loosely coupled? The answer is obviously that they are tightly coupled. Think of the following facts that apply to module relationships:

  • Modules need to explicitly state which other modules they depend on. In that sense, each module is aware of the existence of other modules it needs.
  • Modules are also coupled to and aware of the APIs exposed by the dependent modules. If module A reads module B and calls an API, it is by using the actual Java type that is available in and exported from module B. Thus, module A knows the internals of module B, at least as much as the types exported by module B and used by module A.

Because of these two factors, it is obvious that this kind of tight coupling results in a very strict and rigid behavior of the modules at runtime. Consider the address book viewer application. The set of modules compiled is the exact set of modules that is involved in execution at runtime. Once modules are compiled, there's no way you can remove one of those modules, replace it with something else, and execute them. The modules involved have to be exactly the same. Although we get an impression of Java 9 modules being building blocks that can be assembled into multiple combinations, that advantage only applies to development time. What we've seen so far is that once the modules are coded and the dependencies established, the result is pretty much a cohesive, unalterable monolith.

Now you might wonder, Well, isn't that what we want? The benefits of reliable configuration require a strict check to make sure the exact modules we intend to have are present, don't they? Well, yes, but we could still have runtime flexibility without giving up reliable configuration. An analogous example for that is in the Java language itself. Even though Java is strictly typed, you can achieve powerful runtime flexibility and loose coupling between types by using the concepts of polymorphism. The idea is that classes don't directly depend on each other. Instead, they depend on abstract classes or interfaces. At runtime, instances of those interfaces can be dynamically initialized and used anywhere the interface is used. Could we have something similar to this with modules? If so, how would it work?

Let me give you an example. We have a sorting utility module called packt.sortutil that has an API to sort lists. We have configured the module to export an interface and encapsulate an implementation, but in reality, that distinction is currently useless. It has only one implementation, and all that the module can do now is bubble sort. What if we wanted to have multiple sorting modules and we let the consuming module choose which sorting algorithm to use?

Current:

What we'd like:

We'd like to be able to use multiple modules providing different implementations of sorting in our application. However, thanks to tight coupling, in order for a module to use another module, it has to require it. This means that the consumer packt.addressbook module has to declare requires on each and every one of those different implementation modules it might need, even though, at any time, it might possibly be using just one. Wouldn't it be nice if there was a way you could define an interface type somewhere and have the consumer module depend only on that? Then the different provider modules with different sorting algorithms just provide implementations of the interface that you can plug in at runtime without needing explicit dependencies, and with no coupling between the actual consumer and the various implementation modules?

The following diagram shows what we'd like. Rather than packt.addressbook requiring all of the modules that provide the implementation logic, it instead requires a single module that somehow acts as an interface, and has some mechanism to get the implementations dynamically:

By now you must have guessed that anytime I ask the Wouldn't it be nice ... question, it probably means that such a feature already exists! At least in this case, it's true. This is where services come in. The concept of services and the Service API together add a whole new layer of indirection on top of the existing modularity concepts you've learn so far. Let's dive into the details.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.75.70