Ravi Gupta
6 min readJul 6, 2018

This is the fourth edition of the series. For third edition, click here.

Readability

Modules relation

When one module requires another module, it leads to a special relation between modules called readability. In the above example, easytext.cli module reads easytext.analysis but what exactly does it mean to read another module? We can define it as “when a module reads another module, it means that it can access all public types from the exported packages of the target module”. In the above picture, easytext.analysis has two parts public exported part and an internal encapsulated part. Here only the public parts are accessible by easytext.cli module not the internal parts even if it contains public classes.

This is a pretty big change for Java because it actually means that public no longer are public, at least not in the way we are used to. Previously, if you had a public class in a JAR, it could be access by any other class in the Classpath. Now, if you have public class in the module, there is more question you need to ask before you know if you can actually access that class from another module.

1. The first one, and the most obvious one “do you actually read the module?” That is do you have a require statement in your module descriptor to the other module.

2. The second question then is “ is the type in the package exported by the module that you are requiring?” Because if you have readability to your module, you still can only see the exported packages.

3. The last question to answer is “is the class actually public?” Because Java access control mechanism are still in place now.

Unlike Module resolution process, readability is not transitive. The example shown above illustrates this. The question here is “does easytext.cli because it reads easytext.analysis, also reads the library module?” And the answer is NO. It is not transitive and it is by design.

The easytext.cli module has nothing to do with the library and shouldn’t be burdened with transitive readability on library. You can think of some cases where having transitive readability is a good thing that’s what we will discuss next.

Implied Readability

Let’s look at the example where transitive readability is important. For that we are going to look at the java.sql platform module. It requires two other modules to work. Note the important bit here is that it exposes a driver interface, the java.sql module exports the java.sql package containing the driver interface. What I have shown above is just a little bit of the driver interface with the single method getParentLogger displayed. It returns a Logger and the Logger comes from the java.logging module. How would this work if we have an application module that requires java.sql and wants to use the driver interface? Here we have our application module , which requires java.sql but since readability is not transitive, it cannot read from java.xml and java.logging by default. Our application is using the driver interface and call warning on this logger intent that has been returned from the interface. If we try to compile this application in this setup, it will result in a compile error which says warning in logger is defined in an inaccessible class or interface. What happens is that we can read the driver interface because it comes from java.sql module and we require that module but it returns something of type logger which our application cannot read. At first site, it may be strange that the compiler is warning about the Logger class to us because we are not referencing logger module anywhere explicitly in our application code. However, we do call a warning method which is defined in logger and so the compiler needs to know about the logger in order to verify our call to warning and that’s why our application needs to read to logging module as well.

One solution that may spring to mind is adding a require statement to the module descriptor of our application to java.logging and indeed, this would solve the problem but the solution isn’t really satisfactory. Solving it like this would mean that every module using java.sql is forced to add java.logging as require statement. This makes java.sql module harder to use because you need more information than just requiring the module itself. It would be better if java.sql module somehow convey this information to its consumers. So, what should we do?

The module system offers a special require statement for this exact use case and it’s called requires transitive. Requires transitive does two things for java.sql module. First, it makes sure that java.sql reads java.logging and java.xml just as before but in addition it offers so-called implied readability for the consumers of java.sql which means that modules requiring java.sql now automatically reads java.logging and java.xml as well. In the application module now we can call any method on any public class of java.sql without having to worry about the transitive dependencies.

On the one hand, encapsulating coding in modules is good thing, but on the other hand it will bring some complication to our code.

Limits of Encapsulation
1.
Depends on implementation and not on interface
2. Implementation classes needs to be exported
3. Tight coupling between modules
4. Not extensible

One of the goals of creating the modular applications is to make them extensible and so far, it seems like we have done a good job. What if we want to add more modules? What does the picture look like then?

Analysis modules

Let’s say we have four different implementations of some analysis algorithms shown above as 1, 2, 3 and 4. Each implementation lives in its own module because it can be independently used and we can hide the implementation details from other modules. So far so good, you would say, but this picture is incomplete. We also need to think about the consumer modules.

Module dependency graph

In this we add two modules namely easytext.cli and easytext.gui module both wanting to use all of the analysis module, you can already see it becomes very unwieldy. Suppose later on there comes another analysis module easytext.analysis5, we need to update both cli and gui modules to this new requirement. Clearly, there is a very tight coupling.

There is a second, arguably bigger problem here. As you can see to use analysis one by the cli modules, it needs to instantiate the analysis class of the module and same for all other analysis modules. This looks okay at first sight but when you think about it, it’s not really what you want because whenever a new implementation is added, you need to know about the exact implementation class in your consumer modules in order to instantiate and call the methods.
A more natural way of doing this would be to introduce an interface that unifies the functionality across the different analysis implementations.
All these issues show us that we are hitting the limits of what encapsulation can do for us in these scenarios.

Of course, there is no problem that cannot be solved and we are going to look at it in the next chapter.

Click here, for next edition.

source:
https://www.geekboots.com
https://www.pluralsight.com

Ravi Gupta

Hard work beats talent when talent fails to work hard.