How to create a well designed Java application? SOLID and GoF design patterns strive for tight encapsulation, loose (or low) coupling and high cohesion
A software application is built by coupling various classes, modules, and components. Without coupling, you can't build a software system. But, the software applications are always subject to changes and enhancements. So, you need to build your applications such a way that they can easily adapt to growing requirements and easy to maintain and understand. 3 key aspects to achieve this are
1. Tight encapsulation.
2. Loose (or low) coupling.
3. High cohesion.
The purpose of SOLID design principles and GoF design patterns is to achieve the above goal.
What is encapsulation?
Encapsulation is about hiding the implementation details. This means
1) Hiding data by setting member variables with private access level and providing public methods with pre and post condition checks to access the member variables.
2) Coding to interface and not implementation: Using a given class or module by its interface, and not needing to know any implementation details.
Badly Encapsulated: Directly dependent on ProductServiceImpl
Tightly Encapsulated: Dependent on the interface ProductService. The implementation can change from ProductServiceImpl to InternationalizedProductServiceImpl without the callers or clients OrderServiceImpl, SalesEnquiryServiceImpl, and CustomerServiceImpl needing to know about the implementation details.
SOLID Principle: LSP (Liskov substitution principle), DIP (Dependency inversion principle), and SRP (Single Responsibility Principle) strives to create tight encapsulation.
Q. Does tight encapsulation promote loose coupling?
A. Yes, poor encapsulation can allow tight coupling as it will be possible to access non-encapsulated variables directly from another class, making it dependent on the implementation of the class that contains those variables. It is also possible to have poor encapsulation with loose coupling. So, encapsulation and coupling are different concepts, but poor encapsulation makes it possible to create tight coupling.
What is coupling?
Coupling in software development is defined as the degree to which a module, class, or other construct, is tied directly to others. You can reduce coupling by defining intermediate components (e.g. a factory class or an IoC container like Spring) and interfaces between two pieces of a system. Favoring composition over inheritance can also reduce coupling.
Let's write some code for the above UML diagram to demonstrated loose coupling with GoF factory design pattern.
Step 1: Define the interfaces for the above diagram
public interface OrderService { abstract void process(Product p); }
import java.math.BigDecimal; public interface ProductService { abstract BigDecimal getDiscount(Product p); }
Step 2: Define the implementation class for ProductService
public class ProductServiceImpl implements ProductService { @Override public BigDecimal getDiscount(Product p) { //logic goes here return null; } }
Step 3: Factory eliminate need to bind application specific classes into the code. The code only deals with the ProductService interface , and can therefore work with any user-defined concrete implementations like ProductServiceImpl or InternationalizedProductServiceImpl.
public final class ProductServiceFactory { private ProductServiceFactory(){} public static ProductService getProductService() { ProductService ps = new ProductServiceImpl(); // change impl here return ps; } }
Step 4: The caller or client class who is loosely coupled to ProductService via the ProductServiceFactory.
import java.math.BigDecimal; public class OrderServiceImpl implements OrderService { ProductService ps; @Override public void process(Product p) { ps = ProductServiceFactory.getProductService(); BigDecimal discount = ps.getDiscount(p); //logic } }So, if you want to change over from ProductServiceImpl to InternationalizedProductServiceImpl, all you have to do is make a change in the ProductServiceFactory class. An alternative approach to using a factory is to use an IoC container like Spring instead of a factory class to loosely wire the dependencies via dependency injection at run time.
Q. How do you create loose coupling?
A. By abstracting many of the implementation needs into various interfaces and introducing the concepts of OCP and DIP, you can create a system that has very low coupling.
- Open/Closed Principle (OCP) from the SOLID OO principles promotes loose coupling
- Favor composition over inheritance for loose coupling
- Dependency Inversion Principle for loose coupling
- Event Driven Programming loosely couples producer and consumer
What is cohesion? Cohesion is the extent to which two or more parts of a system are related and how they work together to create something more valuable than the individual parts. You don't want a single class to perform all the functions (or concerns) like being a domain object, data access object, validator, and a service class with business logic. To create a more cohesive system from the higher and lower level perspectives, you need to break out the various needs into separate classes.
Coupling happens in between classes or modules, whereas cohesion happens within a class.
Q. How do you create high cohesion?
A. You can get higher cohesion with a combination of low coupling and SRP (Single Responsibility Principle from SOLID), which allows you to stack a lot of small pieces (e,g. Service, DAO, Validator, etc) together like puzzles to create something larger and more complex.
So, think, tight encapsulation, loose (low) coupling, and high cohesion.
Labels: Architecture, design pattern