Java Design Patterns

Introduction

Design patterns are the best software practices a programmer can use to solve common problems when designing an application or system.

Reusing design patterns help prevent subtle issues that cause major problems, and it also improves code readability for coders and architects who are familiar with the pattern.

In this article, I will share the implementations of some popular Java-based design patterns.

  • Singleton Design Patterns

    Single Design Patterns are part of creational design patterns which means it is related to object creation for java classes. In singleton classes, only one instance of the class should be created and other classes should be able to get the instance of the Singleton class. It is mainly used in Logging, Cache, Session, and Drivers.

Implementation Steps:-

  • The constructor should be private

  • Public method for returning instance.

  • Instance type- private static

Code:-

class Singleton{
    private static Singleton instance;

    private Singleton(){};

    public static Singleton getInstance(){
        if(instance == null){
            instance=new Singleton();
        }

        return instance;
    }
}

public class SingletonExample {

    public static void main(String[] args){
        Singleton instance=Singleton.getInstance();
        System.out.println(instance); // o/p:- Singleton.Singleton@3fee733d
        Singleton instance2=Singleton.getInstance();
        System.out.println(instance2); // o/p:- Singleton.Singleton@3fee733d
    }
}

  • Factory Design Pattern

Factory Design Pattern is part of creational design patterns. It is used when we have multiple sub-classes of a superclass & based on input we want to return one class instance.

Factory class- design implementation

  • superclass can be an interface or abstract class or basic class.

  • Factory class has a static method that returns the instance of subclass based on input.

Code :

abstract class Vehicle{
    public abstract int getWheel();

    public String toString(){
        return "Wheel: "+this.getWheel();
    }
}

class Car extends Vehicle{
    int wheel;

    Car(int wheel){
        this.wheel=wheel;
    }

    @Override
    public int getWheel() {
        return this.wheel;
    }
}

class Bike extends Vehicle{
        int wheel;

        Bike(int wheel){
            this.wheel=wheel;
        }

    @Override
    public int getWheel() {
        return this.wheel;
    }
}

class VehicleFactory{
    public static Vehicle getInstance(String type,int wheel) {
        if (type == "car") {
            return new Car(wheel);
        } else if (type == "bike") {
            return new Bike(wheel);
        }
        return null;
    }

}
public class FactoryDesignExample {
    public static void main(String[] args) {
        Vehicle car=VehicleFactory.getInstance("car",4);
        System.out.println(car); // o/p:- Wheel: 4

        Vehicle bike=VehicleFactory.getInstance("bike",2);
        System.out.println(bike); // o/p:- Wheel: 2
    }
}

  • Builder Design Pattern

    It is a creational design pattern (related to object creation). It is used when we have too many arguments to send in the constructor & it is hard to maintain the order. when we don't want to send all params in object initialization.

    Builder design pattern - implementation

    a. we create a static nested class, which contains all args of outer class.

    b. as per naming convention, if the class name is 'Vehicle', the builder class should be 'VehicleBuilder'

    c. Builder class has a public constructor with all required parameters.

    d. Builder class should have methods for opt parameters. Method will return the builder object.

    e. A 'build()' method that will return the final object.

Code :


class Vehicle{
    //required parameter
    private String engine;
    private int wheel;

    //optional parameter
    private int airbags;

    public String getEngine(){
        return this.engine;
    }

    public int getWheel(){
        return this.wheel;
    }

    public int getAirbags(){
        return this.airbags;
    }

    private Vehicle(VehicleBuilder builder){
        this.engine=builder.engine;
        this.wheel=builder.wheel;
        this.airbags= builder.airbags;
    }

    public static class VehicleBuilder{
        private String engine;
        private int wheel;

        private int airbags;

        public VehicleBuilder(String engine,int wheel){
            this.engine=engine;
            this.wheel=wheel;
        }

        public VehicleBuilder setAirbags(int airbags){
            this.airbags=airbags;
            return this;
        }

        public Vehicle build(){
            return new Vehicle(this);
        }
    }
}

public class BuilderDesignExample{

    public static void main(String[] args) {
        Vehicle car=new Vehicle.VehicleBuilder("1500cc",4).setAirbags(4).build();
        Vehicle bike=new Vehicle.VehicleBuilder("250cc",2).build();

        System.out.println(car.getEngine()); //o/p:- 1500cc
        System.out.println(car.getWheel());  //o/p:- 4
        System.out.println(car.getAirbags());//o/p- 4

        System.out.println(bike.getEngine());//o/p:- 250cc
        System.out.println(bike.getWheel());//o/p:- 2
        System.out.println(bike.getAirbags());//o/p:- 0
    }
}

  • Prototype Design Patterns

It is a creational design pattern (used in object creation). It is used when you want to avoid multiple object creation of the same instance, instance you copy the object to a new object then we can modify as per requirement.

Implementation:-

  • the object which is being copied should provide a copying feature by implementing the cloneable interface.

  • We can override clone() method to implement as per our need.

Code:

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

class Vehicle implements Cloneable{
    private List<String> vehicleList;

    public Vehicle(){
        this.vehicleList=new ArrayList<String>();
    }

    public Vehicle(List<String> list){
        this.vehicleList=list;
    }

    public void insertData(){
        vehicleList.add("Honda Amaze");
        vehicleList.add("Audi A4");
        vehicleList.add("Hyundai Creta");
        vehicleList.add("Maruti Baleno");
    }

    public List<String> getVehicleList(){
        return this.vehicleList;
    }


    @Override
    public Vehicle clone() throws CloneNotSupportedException{
       List<String> tempList=new ArrayList<String>();

       for(String s:this.getVehicleList()){
           tempList.add(s);
       }

       return new Vehicle(tempList);
    }
}

public class PrototypeDesignExample {
    public static void main(String[] args) throws CloneNotSupportedException {
        Vehicle a=new Vehicle();
        a.insertData();

        Vehicle b=(Vehicle) a.clone();
        List<String> list=b.getVehicleList();
        list.add("Honda new Amaze");

        System.out.println(a.getVehicleList());
        //o/p:- [Honda Amaze, Audi A4, Hyundai Creta, Maruti Baleno]
        System.out.println(list);
        //o/p:- [Honda Amaze, Audi A4, Hyundai Creta, Maruti Baleno, Honda new Amaze]

        b.getVehicleList().remove("Audi A4");
        System.out.println(list);
        //o/p:- [Honda Amaze, Hyundai Creta, Maruti Baleno, Honda new Amaze]
    }
}

  • Composite Design Pattern

    It is a structural design pattern (it improves the structure of code) composite lets the client treat individual obj (leaf) and composition of the object (composition) uniformly 4 participants- component, leaf, composite, client. if obj is a leaf node,req is handled directly. if obj is composite, it forward req to the child to do some operations and combines operation

    Implementation

    Component: Account class which contains common method

    Leaf: DepositeAccount & SavingAccount

    Composite: CompositeAccount

    Client: Client class

Code:


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

abstract class Account{
    public abstract float getBalance();
}

class DepositAccount extends Account{
    private String accountNo;
    private float accountBalance;

    public DepositAccount(String accountNo, float accountBalance){
        super();
        this.accountNo=accountNo;
        this.accountBalance=accountBalance;
    }

    @Override
    public float getBalance() {
        return accountBalance;
    }
}

class SavingAccount extends Account{
    private String accountNo;
    private float accountBalance;

    public SavingAccount(String accountNo,float accountBalance){
        super();
        this.accountNo=accountNo;
        this.accountBalance=accountBalance;
    }

    @Override
    public float getBalance() {
        return accountBalance;
    }
}

class CompositeAccount extends Account{
    private float totalBalance;
    private List<Account> accountList=new ArrayList<Account>();

    @Override
    public float getBalance() {
        totalBalance=0;
        for(Account f: accountList){
            totalBalance=totalBalance+f.getBalance();
        }
        return totalBalance;
    }

    public void addAccount(Account acc){
        accountList.add(acc);
    }

    public void removeAccount(Account acc){
        accountList.add(acc);
    }
}


public class CompositeDesignExample {

    public static void main(String[] args) {
        CompositeAccount component=new CompositeAccount();
        component.addAccount(new DepositAccount("DA001",100));
        component.addAccount(new DepositAccount("DA002",150));
        component.addAccount(new DepositAccount("SA001",200));

        float totalBalance= component.getBalance();
        System.out.println("Total Balance: "+ totalBalance); //o/p :- Total Balance: 450.0
    }

}

Summary

In this post, we have learned about java design patterns.

Thank you for reading. If you found this article helpful, react and share. Cheers.