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.