© Alexandru Jecan  2017

Alexandru Jecan, Java 9 Modularity Revealed, https://doi.org/10.1007/978-1-4842-2713-8_1

1. Modular Programming Concepts

Alexandru Jecan

(1)Munich, Germany

You’ve almost surely had to deal with complexity in your software projects. The complexity level of a software application is usually low when development begins, but after a while the complexity begins to increase due to the changes performed on the platform. Complexity keeps increasing constantly as new features are added and the existing functionality is customized. The more changes and customizations are performed, the more complex the system becomes—it may get so complex that it becomes difficult for a new developer to ramp up the project and be able to understand all its inner workings. And if the documentation of the system software isn’t good enough, then understanding the system becomes even harder. A high level of complexity requires more energy, resources, and time to be spent in order to understand the inner structure of the application.

What is it that causes software systems to become so difficult to maintain? The answer is related to the fact that there are a lot of existing dependencies throughout the code. That happens when a piece of code depends on many other pieces of code, and it can generate a lot of issues. Enhancing such a system becomes painful because making a change on one place may affect many other parts of the application. By modifying an application in many different areas, the risk of introducing new errors grows. Besides that, reaching a satisfactory level of reuse becomes very difficult. The software has so many dependencies that simply reusing a component can become costly in terms of time and it could also further increase complexity. This also hinders the desire to enhance the system. By having a system with many dependencies, adding new functionality becomes a nightmare. Furthermore, the testing process also becomes more difficult because testing separate components is almost impossible to achieve. For you as a developer, understanding every part of the system is hard due to its complexity. As new features are added on a regular basis, and the software system evolves, keeping up to date with the changes can be challenging. In order to mitigate and reduce the negative effects of rising complexity, maintaining the system is mandatory, although maintaining itself becomes demanding in terms of time, effort, and cost.

What do we need in order to get rid of these problems? The answer is modularity.

General Aspects of Modularity

Modularity specifies the interrelation and intercommunication between the parts that comprise a software system. Modular programming defines a concept called the module. Modules are software components that contain data and functions. Integrated with other modules, together they form a unitary software system. Modular programming provides a technique to decompose an entire system into independent software modules. Modularity plays a crucial role in modern software architecture . It divides a big software system into separate entities and helps reduce the complexity of software applications while simultaneously decreasing the development effort.

The goal of modularity is to define new entities that are easy to understand and use. Modular programming is a style of developing software applications by splitting the functionality into different modules — software units that contain business logic and have the role of implementing a specific piece of functionality. Modularity enables a clear separation of concerns and assures specialization. It also hides the module’s implementation details. Modularity is an important part of agile software development because it allows us to change or refactor modules without breaking other modules.

Two of the most important aspects of modularity are maintainability and reusability, both of which bring great benefits.

Maintainability

Maintainability refers to the degree to which a software system is upgraded or modified after delivery. A big, monolithic software application is hard to maintain, especially if it has many dependencies inside the code.

The architecture of the system and the design patterns used help us create maintainable code. Maintainability is often ensured by simplicity. For instance, one of the simplest ways to improve maintainability is to provide a reference only to the interface that is implemented by the class as a substitute of the class itself. Low maintainability is a consequence of technical debt. Duplicating code may sometimes decrease the level of maintainability. For example, if one piece of code is altered, then other pieces of code that are similar to it also require the same sort of modifications. Because the code is in are many locations, it’s easy to omit some of the code segments that have to be modified, and this introduces new software issues into the system. The level of maintainability is associated with the quality of the software: the higher the degree of maintainability, the higher the quality of software. Maintainability is enhanced as a result of splitting a monolithic application into a set of modules that present well-defined boundaries between them. In a modular software application, changing a module is easier when it has fewer incoming and outgoing dependencies.

Reusability

Object-oriented programming can be used to obtain reusability, especially via inheritance. In order to reuse the functionality encapsulated in an object, a second object must inherit the first object.

How do modules relate to reusability? It should be possible to reuse a module elsewhere in the same application or in other application. Reusability is the degree to which we can reuse or replace a module. Reusability avoids duplicating code and reduces the number of lines of code, which has a positive impact on the number of software defects. It not only improves software quality, it also helps in developing software faster and makes performing updates on it easier. By applying reusability, the functionality is replicated in a coherent form throughout the entire software system.

Reusability makes the developer’s job easy because it increases their productivity when developing software components. Modules can be reused because they implement a well-defined interface that makes communication with other modules possible. The interface, which is specified as a contract, allows modules to be exchanged. The module interface is expressed in a standard way so that it may be understood and recognized by other modules. In order to achieve a high degree of software reusability, a module should perform a well-defined function. A “design once, deploy many times” software architecture is realized by taking advantage of source code reusability. As a property of good software design, reusability is increased by reducing the dependencies between the modules.

Reusability plays an important role in the migration of applications and libraries. Migration becomes simpler when you can reuse software components or modules. Reusability is not easy to achieve because it is challenging to design software that must be successfully used to fit somewhere else.

Module Definition

A software module is an independent and deployable software component of a larger system that interacts with other modules and hides its inner implementation. It has an interface that allows inter-modular communication. The interface defines which components it provides for external use and which components it requires for internal use. A module determines a boundary by specifying which part of the source code is inside the module. It also provides flexibility and increases the reusability of the software system.

Modules can be discovered starting from compile-time. A module can expose some of its classes to outside or can encapsulate them in order to prevent external access. Figure 1-1 illustrates this concept with an example of a module containing classes that are exposed to outside (classes in green color) and classes that are not exposed to outside (classes in red color).

A431534_1_En_1_Fig1_HTML.gif
Figure 1-1. A module specifies the non-encapsulated (green) and the encapsulated classes (red)

A module can also be viewed as a black box. It has an input and an output and performs a specific function . It takes the input, applies some business logic on it, and returns an output, as illustrated in Figure 1-2.

A431534_1_En_1_Fig2_HTML.gif
Figure 1-2. A module seen as a black box

A software module is reusable , testable, manageable, and deployable. Multiple modules can be combined together to form a new module. Modular programming is the key to reducing the number of bugs in complex software systems to a minimum. By dividing the application into very small modules, each modules will have fewer defects because its functionality is not complex. Assembling these less error-prone modules results in an application with fewer errors.

One of the key facets of modularity is breaking the application down into small, thin modules that are easy to implement because they don’t possess a high level of complexity. The modules can be interconnected earlier at compile time or later at runtime. Each module must be able to be bound to the core application.

Figure 1-3 shows the general structure of a module.

A431534_1_En_1_Fig3_HTML.gif
Figure 1-3. General structure of a software module

A module generally consists of two parts: the module interface and the module implementation. The module interface defines the objects that it exports and the objects that it imports. The exported objects are the objects that are suited to being available outside of the module. The imported objects are the objects that the module requires from outside for internal use. The module implementation defines the variables, constants, and implementation of methods.

Using a module as a variable, instance variable, constant, or function isn’t allowed. A module can consist of objects that can be used only internally inside the module and objects that can be exported to the other modules for external use. Data abstraction, a core concept of modularity, is achieved by hiding information so that it won’t be accessible from outside unless it’s explicitly specified by an export. By default, the internal structure and internal implementation of a module are hidden from other modules.

A change performed on a specific module should not have an impact on other modules. Additionally, it should be possible to add a new module to the core system without breaking the system. Because only the interface of a module is visible from outside the module, it should be possible for developers to change the module’s internal implementation without breaking the code in the application. The structure of a modular software application is basically defined by the connections and correlations between modules.

Some of the characteristics of a module include the following:

  • A module must define interfaces for communication with other modules.

  • A module defines a separation between the module interface and the module implementation.

  • A module should present a set of properties that contain information.

  • Two or more modules can be nested together.

  • A module should have a clear, defined responsibility. Each function should be implemented by only one module.

  • A module must be able to be tested independently from other modules.

  • An error in a module should not propagate to other modules.

Let’s give a short example. If we have two Jigsaw modules called A1 and A2 and one package in module A2 called P2 that we want to be accessible in module A1, then the following conditions have to be met:

  • Module A1 should depend on module A2; module A1 should specify in its declaration that it “requires” module A2.

  • Module A2 should export the package P2 in order to make it available to the modules that depend on it. In our case, in the declaration of the module A2 we should specify that it “exports” package P2.

Package P2 will be accessible in module A1 only if both of conditions are met at compile-time. If none or just one of the conditions mentioned above is met, then package P2 won’t be accessible in module A1. That’s part of the reliable configuration concept introduced in JDK 9. We cover reliable configuration later in this chapter and in upcoming chapters.

The following sections look at four concepts that build the foundation of a modular application:

  • Strong encapsulation

  • Explicit interfaces

  • High module cohesion

  • Low module coupling

Strong Encapsulation

Encapsulation defines the process of preventing data access from outside by allowing a component to declare which of its public types are available to other components and which aren’t. Encapsulation improves code reusability and diminishes the number of software defects. It helps obtain modularity by decoupling the internal behavior of each object from the other elements of the software application.

Related to modularity, encapsulation designates a technique that hides the details of the module implementation. Only the important characteristics of a module should be visible and accessible from other modules. Source code in one module should be able to access a type in another module only if the first module reads the second module and at the same time the second module exports the package that encloses that corresponding type.

In Java prior to version 9, we took advantage of encapsulation by setting the variables and methods of classes to private. In this way, they were accessible only inside the class. We used to define accessor methods like setters and getters as public in order to allow the instance variables to be read or modified from outside of the class.

You will see in the following sections how we can achieve strong encapsulation in Java 9 using modules and the new types used for accessibility.

Explicit Interfaces

The interfaces of a modular system should be as small as possible. If an interface is too big, it should be divided into several smaller interfaces. An interface should make available to a module only the methods that the module really needs in order to be able to fulfill its business requirements.

A modular system typically provides module management and configuration management. Module management refers to the capacity to install, uninstall, and deploy a module. The installation could be done from a module repository, for example. In some cases, a module could be deployed instantly without requiring that the system is restarted. Configuration management specifies the capacity to dynamically configure modules and specify the dependencies between them.

High Module Cohesion

Cohesion measures how the elements of a module are residing together. Module cohesion denotes the module’s integrality and coherence in regard to its internal structure. It expresses the rate to which the module’s items are defining only one functionality.

The highest module cohesion is achieved when all the elements from the module are grouped together to form a piece of functionality. When designing a module, the focus should be on having a high level of cohesion, and this can be accomplished in many different ways:

  • By reducing the complexity of the module (for instance by using fewer methods or less code)

  • By reducing the complexity of the methods described in the module

  • By using related groups of data

  • By defining only one predefined scope for the module

Cohesion describes not only the capability of the module to act like a standalone component in the entire ecosystem, but also the homogeneity of its internal components. High cohesion provides better maintainability and reusability because loosely coupled source code can be altered more simply and with less pain than source code that isn’t loosely coupled.

During the conception of modules, one significant aspect is choosing the right degree of complexity for them. If the functionality of a module is small, then the module might not be very helpful in the entire module ecosystem. If its functionality is complex and it performs a lot of tasks, then it might be troublesome to reuse it. It’s a trade-off, and it’s up to you to make the right decision .

Low Module Coupling

Coupling specifies the level of interdependence between modules. Module coupling refers to the dependency between modules and the way they interact. The objective is to reduce module coupling as much as possible, and this is achieved by specifying interfaces for the inter-modular communication. The interfaces have the role of hiding the module implementation. The resulting modules are independent and can be modified or swapped without fear of breaking other modules—or worse, breaking the entire application.

Low coupling usually corresponds to high cohesion. This is the result we want to achieve in the context of modularity. The reverse—high coupling and low cohesion—is the opposite of what a modular system should normally aim to accomplish.

Tight Coupling vs. Loose Coupling

Tight and loose coupling can refer to classes or modules. Tight coupling between classes is when a class uses logic from another class. It basically uses another class, instantiates an object of it, and then calls the object to access methods or instance variables .

Loose coupling is encountered when a class doesn’t directly use an instance of another class but uses an intermediate layer that primarily defines the object to be injected. One framework that defines loose coupling is the Spring framework, where the dependency objects are being injected by the container into another object. Loosely coupled modules can be altered with less effort. Loose coupling is generally accomplished by using small or medium-sized modules. Replacing a module won’t affect the system if the new module has the same interface as the module being replaced.

Tight coupling means classes are dependent on other classes and it doesn’t allow a module to be replaced so easily because it has dependencies on the implementation of other modules.

Listing 1-1 shows an example of tight coupling. The listing defines one class called Customer that has dependencies on objects of types CurrentAccount, DepositAccount, and SavingsAccount. In the Main class, we create one object of type Customer. This object further creates three other objects. The Customer class contains an object of type CurrentAccount and calls the method depositMoney(amount) on this object. This is a tight coupling between class Customer and class CurrentAccount, and because class CurrentAccount is completely tied to class Customer, it depends on it. The class Customer creates objects of types CurrentAccount, DepositAccount, and SavingsAccount in order to execute some business logic that is defined in these three classes.

Listing 1-1. Defining Three Classes That Are Similar to Each Other
// CurrentAccount.java
package com.apress.tightcoupling;


public class CurrentAccount {

        long deposit;

        public void depositMoney(long amount) {
                deposit = amount;
        }


        public long getDeposit() {
                return deposit;
        }
}


// DepositAccount.java
package com.apress.tightcoupling;


public class DepositAccount {

        long deposit;

        public void depositMoney(long amount) {
                deposit = amount;
        }


        public long getDeposit() {
                return deposit;
        }
}


// SavingsAccount.java
package com.apress.tightcoupling;


public class SavingsAccount {

        long deposit;

        public void depositMoney(long amount) {
                deposit = amount;
        }


        public long getDeposit() {
                return deposit;
        }
}

Listing 1-2 defines a class called Customer that initializes objects of the classes CurrentAccount, DepositAccount, and SavingsAccount .

Listing 1-2. The Customer Class
// Customer.java
package com.apress.tightcoupling;


public class Customer {

        private CurrentAccount currentAccount;
        private DepositAccount depositAccount;
        private SavingsAccount savingsAccount;


        public Customer() {
                currentAccount = new CurrentAccount();
                depositAccount = new DepositAccount();
                savingsAccount = new SavingsAccount();
        }


        public void depositMoneyIntoCurrentAccount(long amount) {

                currentAccount.depositMoney(amount);
        }


        public void depositMoneyIntoDepositAccount(long amount) {

                depositAccount.depositMoney(amount);
        }


        public void depositMoneyIntoSavingsAccount(long amount) {

                savingsAccount.depositMoney(amount);
        }


        public CurrentAccount getCurrentAccount() {
                return currentAccount;
        }


        public DepositAccount getDepositAccount() {
                return depositAccount;
        }


        public SavingsAccount getSavingsAccount() {
                return savingsAccount;
        }
}

Listing 1-3 defines the Main class , which creates three objects of type Customer and calls methods on them.

Listing 1-3. The Main Class
// Main.java
package com.apress.tightcoupling;


public class Main {

    public static void main(String[] args) {

                Customer firstCustomer = new Customer();
                firstCustomer.depositMoneyIntoCurrentAccount(50);


                Customer secondCustomer = new Customer();
                secondCustomer.depositMoneyIntoDepositAccount(100);


                Customer thirdCustomer = new Customer();
                thirdCustomer.depositMoneyIntoSavingsAccount(200);


                System.out.println("First Customer current account amount: " + firstCustomer.getCurrentAccount().getDeposit());
                System.out.println("Second Customer deposit account amount: " + secondCustomer.getDepositAccount().getDeposit());
                System.out.println("Third Customer savings account amount: " + thirdCustomer.getSavingsAccount().getDeposit());
    }
}

The previous three listings show tight coupling in which the Customer class instantiates objects of other classes and subsequently accesses methods on them. This results in a very high level of dependency between the Customer class and the other classes it uses. The main problem is that a change in CurrentAccount, DepositAccount, or SavingsAccount classes could eventually obligate us to adapt the class Customer. For example, if the constructor of CurrentAccount changes, we have a problem. To decouple the classes in this example, we should modify the code so that class Customer is not dependent any more on the implementation of classes CurrentAccount, DepositAccount, and SavingsAccount. As a result, we’ll use an interface in order to make class Customer dependent only on the interface. And we’ll instantiate the other dependencies only in the Main class and not in the Customer class anymore, as we saw before.

Listing 1-4 defines the interface AccountInterface, which contains the definitions of the methods.

Listing 1-4. The Interface AccountInterface
// AccountInterface.java
package com.apress.looseCoupling;


public interface AccountInterface {

        void depositMoney(long amount);

        long getDeposit();
}

Listing 1-5 defines the three classes that implement the interface and provide implementations for the methods from the interface.

Listing 1-5. The Classes CurrentAccount, DepositAccount, and SavingsAccount
// CurrentAccount.java                                                                                                              
package com.apress.looseCoupling;


public class CurrentAccount implements AccountInterface {

        long deposit;

        public CurrentAccount() {
        }


        @Override
        public long getDeposit() {
                return deposit;
        }


        @Override
        public void depositMoney(long amount) {
                deposit = amount;
        }
}


// DepositAccount.java
package com.apress.looseCoupling;


public class DepositAccount implements AccountInterface {

        long deposit;

        public DepositAccount() {
        }


        @Override
        public long getDeposit() {
                return deposit;
        }


        @Override
        public void depositMoney(long amount) {
                deposit = amount;
        }
}


// SavingsAccount.java
package com.apress.looseCoupling;


public class SavingsAccount implements AccountInterface {

        long deposit;

        public SavingsAccount() {
        }


        @Override
        public long getDeposit() {
                return deposit;
        }


        @Override
        public void depositMoney(long amount) {
                deposit = amount;
        }
}

Listing 1-6 presents the Customer class, which uses the interface called AccountInterface inside of its constructor.

Listing 1-6. The Customer Class
// Customer.java
package com.apress.looseCoupling;


public class Customer {

        private AccountInterface account;

        public Customer(AccountInterface account) {
                this.account = account;
        }


        public void deposit(long amount) {
                account.depositMoney(amount);
        }


        public AccountInterface getAccount() {
                return account;
        }
}

Listing 1-7 shows the Main class, which creates three objects of type Customer.

Listing 1-7. The Main Class
// Main.java
package com.apress.looseCoupling;


public class Main {

    public static void main(String[] args) {

                CurrentAccount currentAccount = new CurrentAccount();
                Customer firstCustomer = new Customer(currentAccount);
                firstCustomer.deposit(10);


                DepositAccount depositAccount = new DepositAccount();
                Customer secondCustomer = new Customer(depositAccount);
                secondCustomer.deposit(100);


                SavingsAccount savingsAccount = new SavingsAccount();
                Customer thirdCustomer = new Customer(savingsAccount);
                thirdCustomer.deposit(200);


                System.out.println("First Customer current account amount: " + firstCustomer.getAccount().getDeposit());
                System.out.println("Second Customer deposit account amount: " + secondCustomer.getAccount().getDeposit());
                System.out.println("Third Customer savings account amount: " + thirdCustomer.getAccount().getDeposit());
    }
}

The Customer class is no longer dependent on the other classes . It doesn’t create new classes of type CurrentAccount, DepositAccount, or SavingsAccount, but uses an interface in its constructor. The interface is implemented by all of the three classes. Because CurrentAccount, DepositAccount, and SavingsAccount implement the interface AccountInterface, they’re injected into the Customer object. In the Main class, we create objects of type interface and then pass these objects to the constructor of the Customer class. At the end we call the method deposit() on the Customer objects, which further calls the method depositMoney() from the interface.

In this simple example we’ve seen how to switch from a tightly coupled application to a loosely coupled one by programming to an interface instead of programming to an implementation.

Modular Programming

This section discusses the principles and benefits of modular programming . It compares modular programming and object-oriented programming (OOP) and talks about the differences between a monolithic application and a modular one.

Principles of Modular Programming

The principles of modular programming are continuity, understandability, reusability, combinability, and decomposability. I’ve already talked about reusability, so now let’s focus on the other four.

  • Continuity: Refers to the situation when a requirement to change the functionality of the software system should cause changes in as few modules as possible.

  • Understandability: Refers to the fact that each module should be comprehensible as a standalone single unit . Its role should be clear and concise. It’s definitely easier to understand the inner workings of a particular module—which presents a lower level of functionality—than of an entire application. You should avoid situations in which a module fulfills its role only in correlation with some other modules. A module should not cause side issues for other modules.

  • Combinability: Allows us to recombine modules so that a new software application results.

  • Decomposability: Allows us to decompose a monolith into smaller and simpler parts, which should be independently packaged into a different software unit. The resulting software unit should have a simpler structure and a lower level of complexity than the initial monolith. By breaking a system down into logical modules , we can understand the system much better and adjust it more easily.

Benefits of Modular Programming

We stress some important advantages of modular programming throughout this book, especially when we talk about Project Jigsaw, which brings modularity to Java 9. As you know by now, modular programming reduces the complexity of software applications. And it facilitates the reuse of software components. Moreover, it generally helps make debugging applications easier because only a single module can be debugged and not the entire monolith.

Modular programming allows a team to work together on the same project at the same time with fewer problems related to source code conflicts. If every developer works on their own module, there won’t be any conflicts at all. Having to write less code is another benefit of using modular programming and it is a direct consequence of the reuse capability. By using modular programming techniques, developers gain better productivity and performance by taking advantage of parallel development.

Faster development is another key aspect of modular programming. The time required for development decreases because modules can be designed and implemented independently. The development process can be scaled because it’s possible to develop more modules simultaneously by involving a larger team that can work on different modules at the same time. If a module is being modified, then the other modules will continue to work.

Modules should be easily interchanged with other modules. By defining an interface for the other modules of the application, changing or replacing a module implies only assuring that the new interface is equivalent to the old one. The internal implementation of the new module can differ.

Another important aspect refers to the testing process. Instead of testing an entire application as a whole monolith, the application is divided into modules, and each module is tested separately. Because the modules are independent, multiple modules can be tested at the same time, which speeds test execution and assures the integrity of the modules. By testing each module separately as a unit, better test coverage is achieved. Integration testing is performed by connecting the modules and looking at them as black boxes.

Now that you know what the principles and benefits of modular programming are, it’s time to make a comparison between modular programming and object-oriented programming.

Modular Programming vs. Object-Oriented Programming (OOP)

The similarities between modular programming and OOP include the fact that both break large software applications down into fragments or concerns. Modular programming is not object-oriented. The core principles of OOP, such as polymorphism and inheritance, don’t exist in modular programming. In OOP, polymorphism is used to dynamically change the properties of classes at runtime. This isn’t possible in modular programming because the modules aren’t dynamic. Besides that, in OOP classes use inheritance to enable other classes to inherit variables and method implementations from them. In modular programming, a module can’t inherit another module.

One of the main differences between modular programming and OOP is the fact that in OOP objects can be created from classes. In modular programming, deriving objects from modules isn’t possible.

Monolithic Application vs. Modular Application

A monolithic application is a software application with a high level of complexity that executes an entire group of tasks in order to implement a whole use case. It doesn’t execute only a specific task or function and it doesn’t consist of any logical units that can be identified. It has the role of executing entire functions, not just particular tasks inside these functions. Monolithic applications are constructed without modularity.

Figure 1-4 shows a possible architecture of a monolithic application.

A431534_1_En_1_Fig4_HTML.gif
Figure 1-4. Architecture of a monolithic application

A monolithic application typically consists of many layers: presentation layer, business layer, data access layer, and database. With this system of layers, using multiple technologies for a single layer is difficult. We don’t claim that it’s absolutely impossible—it depends on the technologies you’re using. Sometimes it’s possible to use multiple technologies, and sometimes not. But having such constraints is definitely a drawback for every developer. If they find a third-party library that can solve a specific problem easier, they might not be able to use it because it’s incompatible with the technology used on that layer. It would have been easier for them to be able to split the layers into different microservices and have the freedom to use, for each microservice, whichever technology better suits the task. Being forced sometimes to continue to use the existing technology is bad because the technology may already be old. For a modular system, because you may not have so many dependencies to care about, it is obviously not so time-consuming to update the technology and install the latest version of it, when compared to a monolithic system.

Due to the complexity of the system and the technical know-how required, there may be many cases when multiple teams are working on different layers of the application. To add a new feature to the application, every layer has to be addressed, which means in most cases, when a new feature is added, more than one team has to be involved. That can increase the time needed to develop and test new features because the teams will need to coordinate their work. There may also be more integration work to do—teams will not only have to coordinate with each other, they’ll sometimes also have to wait for certain features to be ready so they can integrate their own piece of developed functionality.

As for scalability, a monolithic system can be scaled only as a whole. It’s impossible to scale only specific parts of the system, and it’s not a good idea to try to scale the entire system if only a part should be actually scaled. Scaling the entire system could mean additional infrastructure costs. This is why monolithic applications aren’t so frequently upgraded and patched compared to modular applications.

A monolithic application can be separated into a set of modules, but that’s not the only way to get a modular application. It can also be designed as modular from the beginning. Redesigning a monolithic application may be not a trivial task, especially when the system is very complex.

Summary

This chapter presented some general aspects on modularity. It discussed two important aspects of modularity: maintainability and reusability. It explained the concept of a software module and underlined the basics of the module declaration. It went over the four main concepts upon which a modular application is built: strong encapsulation, explicit interfaces, high module cohesion, and low module coupling. It presented the benefits of modular programming and compared modular and object-oriented programming. And the chapter illustrated what tight and loose coupling are and showed an explanatory example using Java.

Here’s a short summary of when we should use modular programming:

  • When we have a large program with lots of classes and methods and dependencies between them—such programs are always good candidates for modularization

  • When we want to make a software application more suitable for future developments

  • When we want to get out of monolithic applications

  • When we want to make a software application more understandable

The next chapter explores the basics of Project Jigsaw, the new Java Platform Module System introduced in Java 9.

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

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