Advanced
Data Structures Algorithms & System Design(HLD+LLD)
by Logicmojo

Cracking FAANG companies interviews with a few months of preparation

Learn Advanced Data Structures, Algorithms & System Design

Online live classes from 4 to 7 months programs

Get job assistance after course completion

Download Course Brochure

Design Patterns in Java

TCS Interview Questions

Design patterns are all-purpose answers to common issues in software development. When developing software modules, each pattern serves as a blueprint that enables customization to address a specific design issue in any code. A design pattern is a tried-and-true method of resolving a certain issue or activity. Now, the question of what kind of specific issue will pop into your head. Let me give you an illustration to help.
Problem Stated
Consider making a class for which only one instance (or object) needs to be made and which can be utilised by all other classes.
One thing to keep in mind, though: design patterns are approaches to common issues in object-oriented design that are independent of programming languages. In other words, a design pattern does not reflect a specific implementation but rather an idea.


Learn More

Design Patterns in Java

What do you mean by Design Patterns?

The reusable solutions that address typical issues in software development are called design patterns in Java Language. These issues include duplicate logic, repetitious code, and redundant functions. These enable the developers to significantly reduce the time and effort needed when creating software. By embracing best practises and encouraging reusability for creating reliable code, design patterns are frequently employed in object-oriented software products.


What are some ways to define a design pattern?

The following guidelines are used when describing design patterns:
Name a pattern and specify which category of design pattern it belongs to.
Give an example of a problem and its solution.
What are the possible variants and language-specific solutions to the issue at hand?
What are the software's effectiveness and real-world use cases for these patterns?

What kinds of design patterns are there in Java?

Design patterns come in three different varieties. As follows:
Creational Patterns: By concealing the logic, these patterns offer the flexibility to choose how to create items. The created items are independent of the working system. Factory design pattern, Builder design, Prototype design, Singleton design, and Abstract Factory design are a few examples of creational patterns.
Structural patterns: These patterns aid in specifying how the composition of classes, interfaces, and objects should be defined by the structures of the classes and objects. The Adaptor Design, Facade Design, Decorator Design, Proxy Design, etc. are a few instances of structural patterns.
Behavioural Patterns: These patterns help define how the things should communicate and interact. Several behavioural patterns include Command pattern, Iterator pattern, Observer pattern, Strategy pattern, etc.

What benefits do Java Design Patterns offer?

Design patterns in java used repeatable, template-based approaches that facilitate developers' productive collaboration across several projects. The design patterns in Java are adaptable and make it simple to spot unneeded repetitious code. The software's architecture can be modified to meet specific needs. The following are some benefits of applying design patterns in Java:
They are reusable and adaptable to different projects.
They offer model answers for specifying system architecture.
They make the programme design transparent.
They are tried-and-true methods for quickly creating reliable solutions.

When should we decide to use the design patterns?

Throughout the SDLC's analysis and requirement phase, we must employ design patterns (Software Development Life Cycle). Design patterns facilitate the SDLC's analysis and requirement phase by offering knowledge based on practical experience.

What is Factory design pattern?

The factory design pattern, also referred to as the factory method design pattern, is one of the most well-liked design patterns in Java. According to GoF, this pattern "defines an interface for creating an object, but allows subclasses select which class to instantiate." Using the Factory function, a class can postpone creation for subclasses. This pattern shifts the responsibility for initialising a class from the client to a particular factory class by supplying a specified virtual function Object() { [native code] }. We rely on a factory to provide the objects while hiding the implementation-specific details in order to accomplish this. Using a shared interface, access to the created objects is made feasible.

  //Shape.java
   public interface Shape {
      void draw();
   }

Construct concrete classes that implement the interface.

public class Rectangle implements Shape {

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

public class Circle implements Shape {

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

Create a factory to produce concrete class objects using the supplied data.

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("circle")){
         return new Circle();
         
      } else if(shapeType.equalsIgnoreCase("rectangle")){
         return new Rectangle();
         
      } else if(shapeType.equalsIgnoreCase("square")){
         return new Square();
      }
      
      return null;
   }
}

Utilize the factory to obtain concrete class objects by passing information like type.

public class FactoryPatternDemo {

   public static void main(String[] args) {
      ShapeFactory shapeFactory = new ShapeFactory();

      //Obtain a Circle object and use its draw function..
      Shape shape1 = shapeFactory.getShape("CIRCLE");

      //call draw method of Circle
      shape1.draw();

      //get an object of Rectangle and call its draw method.
      Shape shape2 = shapeFactory.getShape("RECTANGLE");

      //call draw method of Rectangle
      shape2.draw();

      //Obtain a Square object and call the draw function on it.
      Shape shape3 = shapeFactory.getShape("SQUARE");

      //call draw method of square
      shape3.draw();
   }
}

Pros

enables you to conceal the use of an application seam (the core interfaces that make up your application)
enables you to quickly test an application's seam (that is, to fake or stub specific portions of your application) so that you can create and test the other components.
Loose coupling enables you to more easily change the design of your application.

Cons

makes it more challenging to read your code because it is all hidden behind an abstraction that may itself be hiding abstractions.
When used improperly, it might be classified as an anti-pattern. For instance, some people use it to wire up an entire application when utilising an IOC container; instead, use Dependency Injection.

What is singleton design patterns in java?

One of the most straightforward design patterns in the Gang of Four is the Singleton Design Pattern, which is a Creational pattern. The primary goal of this pattern is to construct only one instance of a class and to give that object within the container a single global access point (JVM; in case of Java).

The most frequent justification for only needing one instance of a class—or one object of a class—is to manage access to a shared resource, like a file or a database. However, because some developers utilise it without fully understanding where to apply it in their software, this design pattern is one of the most frequently abused design patterns.

There are a number of considerations to take into account while using the Singleton design pattern. The most typical one is that when you create the instance, you shouldn't accept any arguments. If this is how you wish to receive arguments, you should use the Factory design pattern rather than the Singleton one.

Additionally, be careful not to misuse the Singleton design pattern if you intend to utilise it in your programme. Because there are no other instance variables or references available, unit testing is extremely difficult. Use this design pattern just when needed and not everywhere it is possible.

// No-Thread Safe
public sealed class Singleton1 {  
    private Singleton1() {}  
    private static Singleton1 instance = null;  
    public static Singleton1 Instance {  
        get {  
            if (instance == null) {  
                instance = new Singleton1();  
            }  
            return instance;  
        }  
    }  
}  

// Thread - Safe
public sealed class Singleton2 {  
    Singleton2() {}  
    private static readonly object lock = new object();  
    private static Singleton2 instance = null;  
    public static Singleton2 Instance {  
        get {  
            lock(lock) {  
                if (instance == null) {  
                    instance = new Singleton2();  
                }  
                return instance;  
            }  
        }  
    }  
}  

Pros:

There is only one instance of a class, you can be certain of that.
The instance becomes accessible from anywhere in the world.
Only when the singleton object is requested for the first time is it initialised.

Cons:

When programme components know too much about one another, for instance, the Singleton pattern can hide poor design.
In a multithreaded context, the pattern needs particular handling to prevent several threads from repeatedly creating a singleton object.
violates the idea of one single responsibility. The pattern simultaneously resolves two issues.

What is Abstract Factory Design Pattern?

The abstract factory pattern is a creational pattern that offers a means to encapsulate a collection of distinct factories with a shared purpose without mentioning their particular classes.

The abstract factory has many similarities to the factory technique, which you may be familiar with. But if you're not, I advise you to read Creational Design Pattern: Factory Method. The abstract factory pattern can be thought of as a means to consolidate numerous comparable factories into a single factory with illustrative parameters. These options specify the derived classes and subfactories to employ in your abstract factory class.

A factory of related objects is created through an interface using the abstract factory pattern without the need to explicitly declare the classes of the objects. Each produced factory is capable of dispensing things in accordance with the Factory pattern.

Design a Shapes interface.

public interface Shape {
   void draw();
}

Construct concrete classes that implement the interface.

public class RoundedRectangle implements Shape {
   @Override
   public void draw() {
      System.out.println("Inside RoundedRectangle::draw() method.");
   }
}

public class Rectangle implements Shape {
   @Override
   public void draw() {
      System.out.println("Inside Rectangle::draw() method.");
   }
}

To obtain factories for objects with normal and rounded shapes, create an abstract class.

public abstract class AbstractFactory {
   abstract Shape getShape(String shapeType) ;
}

Create Factory classes that extend AbstractFactory to produce concrete class objects based on input.

public class ShapeFactory extends AbstractFactory {
   @Override
   public Shape getShape(String shapeType){    
      if(shapeType.equalsIgnoreCase("RECTANGLE")){
         return new Rectangle();         
      }else if(shapeType.equalsIgnoreCase("SQUARE")){
         return new Square();
      }	 
      return null;
   }
}

public class RoundedShapeFactory extends AbstractFactory {
   @Override
   public Shape getShape(String shapeType){    
      if(shapeType.equalsIgnoreCase("RECTANGLE")){
         return new RoundedRectangle();         
      }else if(shapeType.equalsIgnoreCase("SQUARE")){
         return new RoundedSquare();
      }	 
      return null;
   }
}

Create a Factory producer/generator class and give in information such as Shape to obtain factories.

public class FactoryProducer {
   public static AbstractFactory getFactory(boolean rounded){   
      if(rounded){
         return new RoundedShapeFactory();         
      }else{
         return new ShapeFactory();
      }
   }
}

By passing information like type, the FactoryProducer may be used to obtain AbstractFactories, which can then be used to obtain factories for concrete classes.

public class AbstractFactoryPatternDemo {
   public static void main(String[] args) {
      //get shape factory
      AbstractFactory shapeFactory = FactoryProducer.getFactory(false);
      //get an object of Shape Rectangle
      Shape shape1 = shapeFactory.getShape("RECTANGLE");
      //call draw method of Shape Rectangle
      shape1.draw();
      //get an object of Shape Square 
      Shape shape2 = shapeFactory.getShape("SQUARE");
      //call draw method of Shape Square
      shape2.draw();
      //get shape factory
      AbstractFactory shapeFactory1 = FactoryProducer.getFactory(true);
      //get an object of Shape Rectangle
      Shape shape3 = shapeFactory1.getShape("RECTANGLE");
      //call draw method of Shape Rectangle
      shape3.draw();
      //get an object of Shape Square 
      Shape shape4 = shapeFactory1.getShape("SQUARE");
      //call draw method of Shape Square
      shape4.draw();
      
   }
}

Benefits

Instead of focusing on implementation, the Abstract Factory design pattern offers a way to write code for interfaces.

Since the Abstract Factory design is a "factory of factories," it is easily expanded to include more products. For instance, we could include a LaptopFactory and a second subclass of the Laptop class.

The robust abstract factory pattern does not use the factory pattern's conditional logic.

What is Adapter Pattern in Java?

A structural pattern is the adapter pattern. It changes a class's interface into a different interface that clients would expect. The adapter pattern can be used to make two incompatible components operate together in a system if we have two incompatible components that we want to utilise but can't use directly.

In this design, a single class is in charge of connecting the capabilities of separate or incompatible interfaces. An actual example would be a card reader that serves as an adaptor between a laptop and a memory card. To read a memory card using a laptop, you must put the memory card into a card reader and the reader into the laptop.

We have an AudioPlayer concrete class that implements the MediaPlayer interface, as well as a MediaPlayer interface. By default, AudioPlayer can play audio files in the mp3 format.

We also have concrete classes that implement the AdvancedMediaPlayer interface and another interface called AdvancedMediaPlayer. VLC and MP4 files can be played by these classes.
We intend to provide support for other file types in AudioPlayer. To achieve this, we developed a media adapter class called MediaAdapter that implements the MediaPlayer interface and makes use of AdvancedMediaPlayer instances to play the necessary formats.

Without knowing the exact class that can play the required format, AudioPlayer utilises the adapter class MediaAdapter, to which the desired audio type is passed. Our demonstration class, AdapterPatternDemo, will play several formats using the AudioPlayer class.

Design interfaces for Advanced Media Player and Media Player.

public interface MediaPlayer {
   public void play(String audioType, String fileName);
}

public interface AdvancedMediaPlayer {	
   public void playVlc(String fileName);
   public void playMp4(String fileName);
}

Implement the AdvancedMediaPlayer interface with concrete classes.

public class VlcPlayer implements AdvancedMediaPlayer{
   @Override
   public void playVlc(String fileName) {
      System.out.println("Playing vlc file. Name: "+ fileName);		
   }

   @Override
   public void playMp4(String fileName) {
      //do nothing
   }
}

The MediaPlayer interface should be implemented in an adapter class.

public class MediaAdapter implements MediaPlayer {

   AdvancedMediaPlayer advancedMusicPlayer;

   public MediaAdapter(String audioType){
   
      if(audioType.equalsIgnoreCase("vlc") ){
         advancedMusicPlayer = new VlcPlayer();			
         
      }else if (audioType.equalsIgnoreCase("mp4")){
         advancedMusicPlayer = new Mp4Player();
      }	
   }

   @Override
   public void play(String audioType, String fileName) {
   
      if(audioType.equalsIgnoreCase("vlc")){
         advancedMusicPlayer.playVlc(fileName);
      }
      else if(audioType.equalsIgnoreCase("mp4")){
         advancedMusicPlayer.playMp4(fileName);
      }
   }
}

the MediaPlayer interface should be implemented in a concrete class.

public class AudioPlayer implements MediaPlayer {
   MediaAdapter mediaAdapter; 

   @Override
   public void play(String audioType, String fileName) {		

      //built-in capability to play MP3 files
      if(audioType.equalsIgnoreCase("mp3")){
         System.out.println("Playing mp3 file. Name: " + fileName);			
      } 
      
      //mediaAdapter is providing support to play other file formats
      else if(audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")){
         mediaAdapter = new MediaAdapter(audioType);
         mediaAdapter.play(audioType, fileName);
      }
      
      else{
         System.out.println("Invalid media. " + audioType + " format not supported");
      }
   }   
}

To play various audio formats, use the AudioPlayer.

public class AdapterPatternDemo {
   public static void main(String[] args) {
      AudioPlayer audioPlayer = new AudioPlayer();

      audioPlayer.play("mp3", "xyz.mp3");
      audioPlayer.play("mp4", "abc.mp4");
      audioPlayer.play("vlc", "pqr.vlc");
      audioPlayer.play("avi", "uvw.avi");
   }
}

What is Builder design pattern?

A complicated item is created utilising small objects and a step-by-step process using a builder pattern. This kind of design pattern falls under the category of a creational pattern because it offers one of the finest ways to make an object.

The Builder design pattern was created to address some issues with the Factory and Abstract Factory design patterns when an object has a large number of characteristics.

When the Object has a lot of properties, there are three main problems with the Factory and Abstract Factory design patterns. Due to the commonality of the argument types and the difficulty of maintaining the argument order from the client side, passing too many arguments from the client programme to the Factory class can lead to errors.

Although some of the parameters may be optional, the Factory pattern forces us to transmit all of the parameters, and those that are not required must be sent as NULL.

Let's look at how the builder design pattern can be used in Java.
You must first construct a static nested class, after which you must copy every argument from the outer class into the Builder class. Following the naming pattern, the builder class should be called ComputerBuilder if the class is called Computer.

Every needed attribute should be passed as a parameter to the public constructor of the Java Builder class.
The Java Builder class should contain methods for setting optional arguments, and after setting an optional property, it should return the same Builder object.

The construct() method in the builder class, which returns the Object required by the client programme, is the last stage. A private constructor in the Class with the Builder class as a parameter is required for this.

public class Computer {
	
	//required parameters
	private String HDD;
	private String RAM;
	
	//optional parameters
	private boolean isGraphicsCardEnabled;
	private boolean isBluetoothEnabled;
	

	public String getHDD() {
		return HDD;
	}

	public String getRAM() {
		return RAM;
	}

	public boolean isGraphicsCardEnabled() {
		return isGraphicsCardEnabled;
	}

	public boolean isBluetoothEnabled() {
		return isBluetoothEnabled;
	}
	
	private Computer(ComputerBuilder builder) {
		this.HDD=builder.HDD;
		this.RAM=builder.RAM;
		this.isGraphicsCardEnabled=builder.isGraphicsCardEnabled;
		this.isBluetoothEnabled=builder.isBluetoothEnabled;
	}
	
	//Builder Class
	public static class ComputerBuilder{

		// required parameters
		private String HDD;
		private String RAM;

		// optional parameters
		private boolean isGraphicsCardEnabled;
		private boolean isBluetoothEnabled;
		
		public ComputerBuilder(String hdd, String ram){
			this.HDD=hdd;
			this.RAM=ram;
		}

		public ComputerBuilder setGraphicsCardEnabled(boolean isGraphicsCardEnabled) {
			this.isGraphicsCardEnabled = isGraphicsCardEnabled;
			return this;
		}

		public ComputerBuilder setBluetoothEnabled(boolean isBluetoothEnabled) {
			this.isBluetoothEnabled = isBluetoothEnabled;
			return this;
		}
		
		public Computer build(){
			return new Computer(this);
		}

	}

}

What is Decorator Design Pattern?

The structural pattern known as the decorator design pattern allows users to add new features to an existing object without changing its structure. By maintaining the class method signatures, this technique generates a new class called a decorator class that serves as a wrapper for the original class. In order to implement the wrapper, this style uses abstract classes and composition-based interfaces. As we categorise functionalities into classes with different concerns, they are mostly utilised to apply the SRP (Single Responsibility Principle). In terms of structure, this pattern resembles the chain of responsibility pattern. The steps to apply the decorator design pattern are as follows:

Create concrete classes that implement the interface and an interface itself.
Create a class for an abstract decorator that implements the interface mentioned above.

Make an extension of the aforementioned abstract decorator class in concrete form.
Decorate the interface objects using the concrete decorator class, then check the results.
Let's use an illustration to better grasp this. In this case, we'll be developing a Shape Interface and two actual classes that implement it: Triangle and Rectangle. The Shape interface will be implemented by the abstract decorator class "ShapeDecorator" that will be created. RedColorDecorator, which extends ShapeDecorator, will be developed. The functionalities will subsequently be implemented using this decorator.

Developing the "Shape" Interface

// Shape.java
   public interface Shape {
       void draw();
   }

Make concrete Shape Interface-implementing classes Triangle.java and Rectangle.java.

 // Rectangle.java
   public class Rectangle implements Shape {
       // Overriding the draw method
       @Override public void draw()
       {
           System.out.println("Rectangle Drawn...");
       }
   }

Make an abstract class called ShapeDecorator that implements the Shape interface.

  public abstract class ShapeDecorator implements Shape {
       protected Shape shapeDecorated;
       public ShapeDecorator(Shape shapeDecorated)
       {
           this.shapeDecorated = shapeDecorated;
       }
       public void draw() { 
           shapeDecorated.draw(); 
       }
   }

Create the ShapeDecorator class extension called RedColorDecorator.java.

 public class RedColorDecorator extends ShapeDecorator {
       public RedColorDecorator(Shape shapeDecorated)
       {
           super(shapeDecorated);
       }
       @Override 
       public void draw()
       {
           shapeDecorated.draw();
           setRedBorder(shapeDecorated);
       }
       private void setRedBorder(Shape shapeDecorated)
       {
           System.out.println("Red color border added...");
       }
   }

To demonstrate the decorator class, implement the Driver class.

 // Driver.java
   public class Driver {
       // Main driver method
       public static void main(String[] args)
       {
       
           Shape triangle = new Triangle();
           Shape redTriangle
               = new RedColorDecorator(new Triangle());
           Shape redRectangle = new RedColorDecorator(new Rectangle());
           // Draw normal triangle
           triangle.draw();
           System.out.println(".........");
           // make the triangle red
           redTriangle.draw();
           System.out.println(".........");
           // make the rectangle red
           redRectangle.draw();
           System.out.println(".........");
       }
   }

What is the MVC pattern of design?

MVC, or model-view-controller, is an acronym. The following list of application issues are divided using this pattern:
Model – This depicts the data-carrying Java POJO object. It could also include the reasoning for updating the controller in the event that the data changes.
View: This shows how the model's data is visualised.
Controlling the data flow into the model and updating the view whenever the model is updated, the controller serves as an interface between the model and the view. The model and the views are kept apart as a result.

What is the primary benefit of creating objects using a prototype design pattern as opposed to a new keyword?

By cloning an existing item's prototype, duplicates of that object are made following the prototype design pattern. The performance of object creation is enhanced by doing this. The new keyword creates objects, which is a resource-intensive and performance-degrading procedure. Therefore, the object made with a new keyword is less favourable than the prototype design pattern.

What is the Observer Pattern?

When there is a one-to-many relationship between items, such as when a modified object needs to automatically notify dependent objects, the observer pattern is employed. The behavioural pattern category includes observer patterns.

Three actor classes are used in an observer pattern. Client, Observer, and Subject An object with attachable and detachable observers to a client object is a subject. We have developed the concrete class Subject, which extends the abstract class Observer.

Establish a Subject class.

import java.util.ArrayList;
import java.util.List;

public class Subject {
	
   private List<Observer> observers = new ArrayList<Observer>();
   private int state;

   public int getState() {
      return state;
   }

   public void setState(int state) {
      this.state = state;
      notifyAllObservers();
   }

   public void attach(Observer observer){
      observers.add(observer);		
   }

   public void notifyAllObservers(){
      for (Observer observer : observers) {
         observer.update();
      }
   } 	
}

Create the class Observer.

public abstract class Observer {
   protected Subject subject;
   public abstract void update();
}

the creation of concrete observer classes

public class BinaryObserver extends Observer{

   public BinaryObserver(Subject subject){
      this.subject = subject;
      this.subject.attach(this);
   }

   @Override
   public void update() {
      System.out.println( "Binary String: " + Integer.toBinaryString( subject.getState() ) ); 
   }
}

Make use of the subject and actual observer objects.

public class ObserverPatternDemo {
   public static void main(String[] args) {
      Subject subject = new Subject();

      new HexaObserver(subject);
      new OctalObserver(subject);
      new BinaryObserver(subject);

      System.out.println("First state change: 15");	
      subject.setState(15);
      System.out.println("Second state change: 10");	
      subject.setState(10);
   }
}


HAVE A QUESTION? GIVE US A CALL OR CONTACT US - WE'D LOVE TO HEAR FROM YOU

PHONE: +91 80889-75867

WhatsApp : Click Here...