By Subham Aggarwal | 2/6/2020 | General |Beginners

Top 3 Design Patterns in Java

Top 3 Design Patterns in Java

Design patterns represent the best practices used by experienced object-oriented software engineers. Design patterns are solutions to general problems that software engineers face during software development. These solutions were obtained by trial and error by numerous software engineers over quite a substantial period of time.

Types of Design patterns

Design patterns can be divided into three categories: Creational, Structural, and Behavioral.

 

  • Creational Patterns - These design patterns provide a way to create objects while hiding the creation logic, rather than instantiating objects directly using new operators. This gives programs more flexibility in deciding which objects need to be created for a given use case.
  • Structural Patterns - These design patterns concern class and object composition. The concept of inheritance is used to compose interfaces and define ways to compose objects to obtain new functionalities.
  • Behavioral Patterns - These design patterns are specifically concerned with communication between objects.

 

Now, let’s look at top 3 design patterns used in Java and Java EE.

Singleton Pattern

The singleton pattern is one of the simplest design patterns in Java. This type of design pattern falls under the creational pattern as this pattern provides one of the best ways to create an object.

 

This pattern involves a single class which is responsible for creating an instance while making sure that only a single object is created. This class provides a way to access its only object which can be accessed directly without the need to instantiate the object of the class.

 

Let’s look at a sample class here:

public class SingletonClass {

  //create an object of SingleObject
  private static SingletonClass instance = new SingletonClass();

  //make the constructor private so that this class cannot be
  //SingletonClass
  private SingleObject(){}

  //Get the only object available
  public static SingletonClass getInstance(){
     return instance;
  }

  public void showMessage(){
     System.out.println("Hello Java!");
  }
}



public class SingletonPatternDemo {
  public static void main(String[] args) {

     //illegal construct
     //Compile Time Error: The constructor SingleObject() is not visible
     //SingletonClass object = new SingletonClass();

     //Get the only object available
     SingletonClass object = SingletonClass.getInstance();

     //show the message
     object.showMessage();
  }
}

Let’s build a quick checklist so that we don’t miss any steps:

  1. Define a private static attribute in the "single instance" class.
  2. Define a public static accessor function in the class.
  3. Do "lazy initialization" (creation on first use) in the accessor function.
  4. Define all constructors to be protected or private.
  5. Clients may only use the accessor function to manipulate the Singleton.

 

The advantage of Singleton over global variables is that you are absolutely sure of the number of instances when you use Singleton, and, you can change your mind and manage any number of instances.

 

The Singleton design pattern is one of the most inappropriately used patterns. Singletons are intended to be used when a class must have exactly one instance, no more, no less. Designers frequently use Singletons in a misguided attempt to replace global variables. A Singleton is, for all intents and purposes, a global variable. The Singleton does not do away with the global, it merely renames it.

Factory Pattern

The factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes.

 

>>> The new operator is considered harmful.

 

To move to implementation quickly, let’s start by creating an interface:

public interface Shape {
  void draw();
}

Let’s now create classes that use this interface.

public class Parallelogram implements Shape {

  @Override
  public void draw() {
     System.out.println("Inside Parallelogram::draw() method.");
  }
}



public class Quadrilateral implements Shape {

  @Override
  public void draw() {
     System.out.println("Inside Quadrilateral::draw() method.");
  }
}

Finally, we create a Factory class to create an object of a concrete class based on the information provided.

public class ShapeFactory {
    
  //use getShape method to get object of type shape 
  public Shape getShape(String shapeType){
     if(shapeType == null){
        return null;
     }        
     if(shapeType.equalsIgnoreCase("Quadrilateral")){
        return new Quadrilateral();
        
     } else if(shapeType.equalsIgnoreCase("Parallelogram")){
        return new Parallelogram();
        
     }
     
     return null;
  }
}

Let’s build a quick checklist so that we don’t miss any steps:

  1. Define a factory interface that consists of one factory method per product.
  2. Define a factory derived class for each platform that encapsulates all references to the new operator.
  3. The client should retire all references to new and use the factory methods to create the product objects.

 

Sometimes creational patterns are competitors; there are cases when either Prototype or Abstract Factory could be used profitably. At other times they are complementary. The Abstract Factory might store a set of Prototypes from which to clone and return product objects, while Builder can use one of the other patterns to implement which components get built. Abstract Factory, Builder, and Prototype can use Singleton in their implementation.

 

Lastly:

  • Abstract Factory classes are often implemented with Factory Methods, but they can also be implemented using Prototype.
  • Abstract Factory can be used as an alternative to Facade to hide platform-specific classes.
  • Builder focuses on constructing a complex object step by step. Abstract Factory emphasizes a family of product objects (either simple or complex). Builder returns the product as a final step, but as far as the Abstract Factory is concerned, the product gets returned immediately.

Decorator Pattern

The decorator pattern allows a user to add new functionality to an existing object without altering its structure. This type of design pattern falls under the structural pattern as this pattern acts as a wrapper to existing class.

 

This pattern creates a decorator class which wraps the original class and provides additional functionality keeping class methods signature intact.

 

To move to implementation quickly, let’s start by creating an interface:

public interface Shape {
  void draw();
}

Let’s now create classes that use this interface.

public class Parallelogram implements Shape {

  @Override
  public void draw() {
     System.out.println("Inside Parallelogram::draw() method.");
  }
}



public class Quadrilateral implements Shape {

  @Override
  public void draw() {
     System.out.println("Inside Quadrilateral::draw() method.");
  }
}

Finally, we create an abstract decorator class implementing the Shape interface.

public abstract class ShapeDecorator implements Shape {
  protected Shape decoratedShape;

  public ShapeDecorator(Shape decoratedShape){
     this.decoratedShape = decoratedShape;
  }

  public void draw(){
     decoratedShape.draw();
  }    
}

Create a concrete decorator class extending the ShapeDecorator class.

public class RedShapeDecorator extends ShapeDecorator {

  public RedShapeDecorator(Shape decoratedShape) {
     super(decoratedShape);        
  }

  @Override
  public void draw() {
     decoratedShape.draw();          
     setRedBorder(decoratedShape);
  }

  private void setRedBorder(Shape decoratedShape){
     System.out.println("Border Color: Red");
  }
}

Use the RedShapeDecorator to decorate Shape objects.

public class DecoratorPatternDemo {
  public static void main(String[] args) {

     Shape parallelogram = new Parallelogram();

     Shape redParallelogram = new RedShapeDecorator(new Parallelogram());

     Shape redQuadrilateral = new RedShapeDecorator(new Quadrilateral());
     System.out.println("Circle with normal border");
     circle.draw();

     System.out.println("\nParallelogram of red border");
     redParallelogram.draw();

     System.out.println("\Quadrilateral of red border");
     redQuadrilateral.draw();
  }
}

Conclusion

Design patterns can speed up the development process by providing tested, proven development paradigms. Effective software design requires considering issues that may not become visible until later in the implementation. Reusing design patterns helps to prevent subtle issues that can cause major problems and improves code readability for coders and architects familiar with the patterns.

By Subham Aggarwal | 2/6/2020 | General

{{CommentsModel.TotalCount}} Comments

Your Comment

{{CommentsModel.Message}}

Recent Stories

Top DiscoverSDK Experts

User photo
3355
Ashton Torrence
Web and Windows developer
GUI | Web and 11 more
View Profile
User photo
3220
Mendy Bennett
Experienced with Ad network & Ad servers.
Mobile | Ad Networks and 1 more
View Profile
User photo
3060
Karen Fitzgerald
7 years in Cross-Platform development.
Mobile | Cross Platform Frameworks
View Profile
Show All
X

Compare Products

Select up to three two products to compare by clicking on the compare icon () of each product.

{{compareToolModel.Error}}

Now comparing:

{{product.ProductName | createSubstring:25}} X
Compare Now