Wednesday, 18 June 2025

 

                                                                  Adapter design pattern

The Adapter design pattern is a structural pattern that allows the interface of an existing class to be used as another interface. It acts as a bridge between two incompatible interfaces, making them work together. This pattern involves a single class, known as the adapter, which is responsible for joining functionalities of independent or incompatible interfaces.

Let's understand this concept using a simple example:

Let's say you have two friends, one who speaks only English and another who speaks only French. You want them to communicate, but there's a language barrier.

  • You act as an adapter, translating messages between them. Your role allows the English speaker to convey messages to you, and you convert those messages into French for the other person.
  • In this way, despite the language difference, your adaptation enables smooth communication between your friends.
  • This role you play is similar to the Adapter design pattern, bridging the gap between incompatible interfaces.

Components of Adapter Design Pattern in Java

1. Target Interface

  • Description: Defines the interface expected by the client. It represents the set of operations that the client code can use.
  • Role: It's the common interface that the client code interacts with.

2. Adaptee

  • Description: The existing class or system with an incompatible interface that needs to be integrated into the new system.
  • Role: It's the class or system that the client code cannot directly use due to interface mismatches.

3. Adapter

  • Description: A class that implements the target interface and internally uses an instance of the adaptee to make it compatible with the target interface.
  • Role: It acts as a bridge, adapting the interface of the adaptee to match the target interface.

4. Client

  • Description: The code that uses the target interface to interact with objects. It remains unaware of the specific implementation details of the adaptee and the adapter.
  • Role: It's the code that benefits from the integration of the adaptee into the system through the adapter.

How Adapter Design Pattern works?

  • Client Request: The client initiates a request by calling a method on the adapter using the target interface.
  • Adapter Translation: The adapter translates or maps the client's request into a form that the adaptee understands, using the adaptee's interface.
  • Adaptee Execution: The adaptee performs the actual work based on the translated request from the adapter.
  • Result to Client: The client receives the results of the call, remaining unaware of the adapter's presence or the specific details of the adaptee.

 

Example :

1. Target Interface

 

package adapterdesignpattern;

 

public interface PaymentGateway {

              

                void pay(double amount);

 

}

 

2. Adaptee

 

package adapterdesignpattern;

 

public class PayPal {

 

               void send(double amount) {

                              System.out.println("Paid $" + amount + " via PayPal.");

               }

 

}

 

package adapterdesignpattern;

 

public class Stripe {

 

               void makePayment(int amount) {

                              System.out.println("Paid $" + amount + " via Stripe.");

               }

              

              

}

 

 

package adapterdesignpattern;

 

public class RozarPay {

 

               void doTransaction(String amount) {

                              System.out.println("Paid $" + amount + " via RozarPay.");

               }

 

}

 

3. Adapter

 

 

package adapterdesignpattern;

 

public class PayPalAdapter implements PaymentGateway {

 

              

               private PayPal payPal=new PayPal();

 

               @Override

               public void pay(double amount) {

                              payPal.send(amount);

                             

               }

              

              

}

 

package adapterdesignpattern;

 

public class StripeAdapter implements PaymentGateway {

 

               private Stripe stripe = new Stripe();

 

               @Override

               public void pay(double amount) {

                              stripe.makePayment((int) amount);

 

               }

 

}

 

 

package adapterdesignpattern;

 

public class RozarPayAdapter implements PaymentGateway {

 

              

               private RozarPay rozarPay=new RozarPay();

               @Override

               public void pay(double amount) {

                              rozarPay.doTransaction(String.format("%.2f", amount));

                             

               }

 

}

 

 

4. Client

 

 

package adapterdesignpattern;

 

public class PaymentGatewayDemo {

 

               public static void main(String[] args) {

 

                              PaymentGateway payPalAdapter = new PayPalAdapter();

                              payPalAdapter.pay(500.0);

 

                              PaymentGateway stripeAdapter = new StripeAdapter();

                              stripeAdapter.pay(800.0);

 

                              PaymentGateway rozarPayAdapter = new RozarPayAdapter();

                              rozarPayAdapter.pay(900.0);

 

               }

 

}

 

O/p:

Paid $500.0 via PayPal.

Paid $800 via Stripe.

Paid $900.00 via RozarPay.

 

 

Where Adapter Pattern is Used in Java

1. Java I/O (Input/Output) API

  • Example: InputStreamReader
    • Use: Adapts a ByteStream (InputStream) into a CharacterStream (Reader).
    • Pattern: Adapter

Reader reader = new InputStreamReader(new FileInputStream("file.txt"));

    • How it works: InputStreamReader adapts the InputStream to be usable as a Reader.

2. Java Collections Framework

  • Example: Arrays.asList(T... a)
    • Use: Converts (adapts) an array to a List.

List<String> list = Arrays.asList("a", "b", "c");

    • How it works: Wraps the array and exposes it as a List.

3. Java AWT/Swing GUI Event Listeners

  • Example: MouseAdapter, KeyAdapter, etc.
    • Use: Adapter classes provide default (empty) implementations so developers can override only needed methods.

frame.addMouseListener(new MouseAdapter() {

    public void mouseClicked(MouseEvent e) {

        System.out.println("Mouse clicked");

    }

});

    • How it works: MouseAdapter adapts the complex MouseListener interface by providing empty methods.

4. JDBC (Java Database Connectivity)

  • Example: Wrapping custom ResultSet or Connection implementations
    • Use: JDBC drivers adapt vendor-specific DB operations to the standard java.sql.Connection, ResultSet, etc.
    • How it works: Adapters ensure all drivers follow the standard JDBC interface, despite underlying DB differences.

5. Third-party Library Integration

  • Use: Adapting a third-party library's class/interface to your application’s expected interface.
    • Example: Suppose a library gives you a LoggerX class, but your code expects a Logger interface — you can create an adapter.

6. Legacy Code Integration

  • Use: When working with older systems whose classes can’t be modified, you write an adapter to make them compatible with your new system.

 

Tuesday, 17 June 2025

 

What is the Builder Pattern?

The Builder Pattern is a creational design pattern that allows you to construct complex objects step by step. It separates the construction logic of an object from its representation, enabling the same construction process to create different representations of the object.


❗ Problem:

When a class has:

  • Many constructor parameters (some required, some optional),

  • Or, needs multiple constructors (telescoping constructors),

  • Or, requires a lot of logic for proper setup,

…it becomes hard to manage, read, and extend.


💡 Solution:

The Builder Pattern helps:

  • Avoid long constructors with many parameters.

  • Allow incremental construction using method chaining.

  • Provide a clear separation of concerns between object creation and usage.


Person p = new Person("govind", "khan", "govindkhan@.gmail.com", 30);

Person p = new Person.Builder("govind")
              .setLastName("khan")
              .setEmail("govindkhan@.gmail.com")
              .setAge(30)
              .build();


Builder in Real-World APIs

  • Java: StringBuilder, StringBuffer, Stream.Builder, Calendar.Builder

  • Lombok: @Builder annotation to generate builders automatically.

  • Jackson / Gson: Often use builders when deserializing complex JSON.

  • Effective Java (Joshua Bloch): Recommends Builder over telescoping constructors.


Example :-

package builderdesignpattern;


public class Person {


// Required Fields

private String firstName;

private String lastName;


// optional field.

private int age;

private String email;


private Person(Builder builder) {

super();

this.firstName = builder.firstName;

this.lastName = builder.lastName;

this.age = builder.age;

this.email = builder.email;

}


public static class Builder {

private String firstName;

private String lastName;

private int age;

private String email;


public Builder(String firstName, String lastName) {

super();

this.firstName = firstName;

this.lastName = lastName;

}


public Builder setAge(int age) {

this.age = age;

return this;

}


public Builder setEmail(String email) {

this.email = email;

return this;

}


public Person build() {

return new Person(this);

}


}


@Override

public String toString() {

return "Person [firstName=" + firstName + ", lastName=" + lastName + ", age=" + age + ", email=" + email + "]";

}


}




package builderdesignpattern;


public class Main {


public static void main(String[] args) {


Person p1 = new Person.Builder("Govind", "Khan")

.setAge(35)

.setEmail("Govindkhan@gmail.com")

.build();

System.out.println(p1);


}


}



o/P :


Person [firstName=Govind, lastName=Khan, age=35, email=Govindkhan@gmail.com]





Monday, 16 June 2025

 

                                                                  SOLID principles IN JAVA

The SOLID principles, conceptualized by Robert C. Martin (aka Uncle Bob), is a fundamental design principles that aim to create well-structured and maintainable code. This article will walk you through the 5 principles with examples.

  1. Single Responsibility Principle (SRP)
  2. Open/Closed Principle (OCP)
  3. Liskov Substitution Principle (LSP)
  4. Interface Segregation Principle (ISP)
  5. Dependency Inversion Principle (DIP)

 

 

1. Single Responsibility Principle (SRP)

  • "A class should have only one reason to change."
  • Each class, method, or function should serve a single, well-defined purpose, with all elements within it supporting that purpose.
  • If a change needs to be made, it should only affect that single responsibility, and not other unrelated parts of the codebase.

// Violates SRP

public class BankAccount {

    private double balance;

 

    public void deposit(double amount) {

        balance += amount;

    }

 

    public void withdraw(double amount) {

        if (balance >= amount) {

            balance -= amount;

        } else {

            System.out.println("Insufficient funds");

        }

    }

 

    public void printBalance() {

        System.out.println("Current balance: " + balance);

    }

}

 

The code above violates SRP because say if the requirement changed to display the balance in a different format, then the BankAccount class would need to be updated, hence violating SRP. To resolve this, we can separate them into 2 classes, ensuring that each class has a single responsibility.

// Follows SRP

public class BankAccount {

    private double balance;

 

    public void deposit(double amount) {

        balance += amount;

    }

 

    public void withdraw(double amount) {

        if (balance >= amount) {

            balance -= amount;

        } else {

            throw new IllegalArgumentException("Insufficient funds");

        }

    }

 

    public double getBalance() {

        return balance;

    }

}

 

public class BalanceDisplayer {

    public void printBalance(BankAccount account) {

        System.out.println("Current balance: " + account.getBalance());

    }

}

2. Open closed Principle (OSP)

  • "Software components should be open for extension but closed for modification."
  • New functionality can be added without changing existing code.

Using the same example above, let's say we want to display the balance in a few formats. To modify the BalanceDisplayer class without violating OCP, we need to design the code in such a way that everyone can reuse the feature by just extending it.

// Interface for balance display

public interface BalanceDisplay {

    void displayBalance(BankAccount account);

}

 

// Implementation for displaying balance in a simple format

public class SimpleBalanceDisplay implements BalanceDisplay {

    @Override

    public void displayBalance(BankAccount account) {

        System.out.println("Current balance: " + account.getBalance());

    }

}

 

// Implementation for displaying balance in a fancy format

public class FancyBalanceDisplay implements BalanceDisplay {

    @Override

    public void displayBalance(BankAccount account) {

        System.out.println("~~~ Fancy Balance: $" + account.getBalance() + " ~~~");

    }

3. Liskov substitution Principle (LSP)

  • "Derived or child classes must be substitutable for their base or parent classes."
  • If B is a subclass of A, B should be able to replace A without affecting the correctness of the program.

Here’s a base class:

public class BankAccount {

    protected double balance;

 

    public BankAccount(double balance) {

        this.balance = balance;

    }

 

    public void deposit(double amount) {

        balance += amount;

    }

 

    public void withdraw(double amount) {

        balance -= amount;

    }

 

    public double getBalance() {

        return balance;

    }

}

 

Now, here's a subclass that respects LSP:

public class SavingsAccount extends BankAccount {

    public SavingsAccount(double balance) {

        super(balance);

    }

 

    // Adds new functionality, doesn't change base behavior

    public void calculateInterest() {

        double interest = balance * 0.03; // 3% interest

        balance += interest;

    }

}

You can replace BankAccount with SavingsAccount without breaking any base functionality like deposit, withdraw, or getBalance.

 

LSP-Violating Example: GoldAccount

This subclass changes core behavior (deposit):

public class GoldAccount extends BankAccount {

    private double bonusPoints;

 

    public GoldAccount(double balance, double bonusPoints) {

        super(balance);

        this.bonusPoints = bonusPoints;

    }

 

    // Modifies base behavior: adds bonus points to deposit

    @Override

    public void deposit(double amount) {

        balance += amount + (bonusPoints * 0.1);

    }

}

This violates LSP because if you write code assuming the behavior of BankAccount.deposit() (which adds exactly the amount), and you substitute it with GoldAccount, the logic now behaves differently.

 

 

public class BankService {

    public static void makeDeposit(BankAccount account, double amount) {

        account.deposit(amount);

        System.out.println("Balance after deposit: " + account.getBalance());

    }

}

 

If we call:

BankAccount account = new GoldAccount(1000, 50);

BankService.makeDeposit(account, 100);

 

Expected balance: 1000 + 100 = 1100

Actual balance: 1000 + 100 + (50 * 0.1) = 1105

 

 

  • SavingsAccount adheres to LSP as it extends the functionality by adding specific methods like calculateInterest, which do not alter the core behavior of depositing and withdrawing funds.
  • GoldAccount class violates LSP by changing the behavior of the deposit method from the base BankAccount class.

 

4. Interface Segregation Principle (ISP)

  • "Do not force any client to implement an interface which is irrelevant to them."
  • Clients should not be compelled to implement interfaces that contain methods they do not use.
  • Instead of having a single large interface, it is better to have multiple smaller interfaces, each focusing on a specific set of methods relevant to a particular functionality.

// Violation of ISP

public interface IBankAccount {

    void deposit(double amount);

    void withdraw(double amount);

    double getBalance();

    void printStatement();

    void requestLoan();

}

 

public class BankAccount implements IBankAccount {

    private double balance;

 

    public void deposit(double amount) {

        // implementation details

    }

 

    public void withdraw(double amount) {

        // implementation details

    }

 

    public double getBalance() {

        // implementation details

    }

 

    public void printStatement() {

        // implementation details

    }

 

    public void requestLoan() {

        // implementation details

    }

}

 

  • The IBankAccount interface violates ISP by including methods that are not relevant to all classes that implement it.
  • The BankAccount class implements the entire IBankAccount interface, even though it may not need the requestLoan method.

// Adheres to ISP

// Interface for account management

public interface AccountManager {

    void deposit(double amount);

    void withdraw(double amount);

    double getBalance();

}

 

// Interface for account reporting

public interface AccountReporter {

    void printStatement();

}

 

// Interface for loan management

public interface LoanManager {

    void requestLoan();

}

Each class now depends only on the interfaces relevant to its responsibilities, adhering to ISP.

5. Dependency Inversion Principle (DIP)

  • "High-level modules should not depend on low-level modules. Both should depend on abstractions."
  • "Abstractions should not depend on details. Details should depend on abstractions."
  • Think of it like a restaurant. The high-level module is the restaurant, and the low-level module is the kitchen. The restaurant should not directly depend on the kitchen. Instead, both should depend on a common language, like English. The kitchen should not depend on the restaurant's specific menu. Instead, the menu should depend on the kitchen's cooking skills.

// Violates DIP

// Low-level class

public class BankAccount {

    private double balance;

 

    public BankAccount(double initialBalance) {

        this.balance = initialBalance;

    }

 

    public void withdraw(double amount) {

        balance -= amount;

        System.out.println("Withdrawn: " + amount + ", Remaining balance: " + balance);

    }

 

    public double getBalance() {

        return balance;

    }

}

 

// High-level class depending directly on low-level class

public class ShoppingMall {

    private BankAccount bankAccount;

 

    public ShoppingMall(BankAccount bankAccount) {

        this.bankAccount = bankAccount;

    }

 

    public void doPayment(String order, double amount) {

        System.out.println("Processing order: " + order);

        bankAccount.withdraw(amount); // tightly coupled

    }

}

 

// Main class

public class Main {

    public static void main(String[] args) {

        BankAccount bankAccount = new BankAccount(1000);

        ShoppingMall mall = new ShoppingMall(bankAccount);

        mall.doPayment("Shoes", 200);

    }

}

Problem:

  • ShoppingMall is directly coupled to BankAccount.
  • If you want to switch to another payment method (e.g., credit card, PayPal), you'll need to change ShoppingMall, which violates DIP.

 

 

In this example, the ShoppingMall class directly depends on the BankAccount class, which violates DIP. The ShoppingMall class is a high-level module, and the BankAccount class is a low-level module.

To fix this, we can introduce an abstraction that both the ShoppingMall and BankAccount classes can depend on.

// Adhering to DIP

// Abstraction

public interface PaymentProcessor {

    void processPayment(double amount);

    double getBalance();

}

 

// Low-level module implementing abstraction

public class BankAccount implements PaymentProcessor {

    private double balance;

 

    public BankAccount(double initialBalance) {

        this.balance = initialBalance;

    }

 

    @Override

    public void processPayment(double amount) {

        balance -= amount;

        System.out.println("BankAccount payment processed. Amount: " + amount + ", Remaining balance: " + balance);

    }

 

    @Override

    public double getBalance() {

        return balance;

    }

}

 

 

 

// High-level module depending on abstraction

public class ShoppingMall {

    private PaymentProcessor paymentProcessor;

 

    public ShoppingMall(PaymentProcessor paymentProcessor) {

        this.paymentProcessor = paymentProcessor;

    }

 

    public void doPayment(String order, double amount) {

        System.out.println("Processing order: " + order);

        paymentProcessor.processPayment(amount);

    }

}

 

// Main.java

public class Main {

    public static void main(String[] args) {

        PaymentProcessor bankAccount = new BankAccount(1000); // Abstraction

        ShoppingMall mall = new ShoppingMall(bankAccount);    // Uses abstraction

        mall.doPayment("Laptop", 300);

    }

}

 

Benefits of This Approach:

  • ShoppingMall is now independent of specific payment implementations.
  • You can easily add new payment methods like CreditCard, PayPalAccount, etc., by just implementing PaymentProcessor, without modifying ShoppingMall.

Add Another Payment Method (Optional Extension)

 

public class PayPalAccount implements PaymentProcessor {

    private double balance;

 

    public PayPalAccount(double balance) {

        this.balance = balance;

    }

 

    @Override

    public void processPayment(double amount) {

        balance -= amount;

        System.out.println("PayPal payment processed. Amount: " + amount + ", Remaining balance: " + balance);

    }

 

    @Override

    public double getBalance() {

        return balance;

    }

}

 

PaymentProcessor paypal = new PayPalAccount(500);

ShoppingMall mall2 = new ShoppingMall(paypal);

mall2.doPayment("Online Course", 150);

Sunday, 27 April 2025

Spring Boot Kafka JsonSerializer and JsonDeserializer Example


package com.app.jsonserializer.config;

import org.apache.kafka.clients.admin.NewTopic;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.config.TopicBuilder;


@Configuration
public class KafkaConfig {

@Bean
public NewTopic topic()
{

return TopicBuilder.name(AppConstants.JSON_SERIALIZER_TOPIC_NAME).build();
}


}

==============================================================

package com.app.jsonserializer.config;

public class AppConstants {

public static final String JSON_SERIALIZER_TOPIC_NAME="json-serializer-topic";

public static final String GROUP_ID="groupid-1";

}


=====================================================================

package com.app.jsonserializer.payload;

public class User {


private int id;
private String firstName;
private String lastName;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

@Override
public String toString() {
return "User{" +
"firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", id=" + id +
'}';
}
}

============================================================================

package com.app.jsonserializer.service;


import com.app.jsonserializer.config.AppConstants;
import com.app.jsonserializer.payload.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.KafkaHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;

@Service
public class KafkaProducer {



private static final Logger LOGGER = LoggerFactory.getLogger(KafkaProducer.class);


@Autowired
private KafkaTemplate<String, User> kafkaTemplate;




public void sendMsg(User data)
{
LOGGER.info(String.format("Message sent -> %s", data.toString()));
Message<User> message=MessageBuilder.withPayload(data).setHeader(KafkaHeaders.TOPIC, AppConstants.JSON_SERIALIZER_TOPIC_NAME).build();
kafkaTemplate.send(message);
}
}

=======================================================================================


package com.app.jsonserializer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class JsonserializerApplication {

public static void main(String[] args) {
SpringApplication.run(JsonserializerApplication.class, args);
}

}

========================================================================


spring.application.name=jsonserializer


spring.kafka.producer.bootstrap-servers: localhost:9092
spring.kafka.producer.key-serializer: org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer: org.springframework.kafka.support.serializer.JsonSerializer


spring.kafka.consumer.bootstrap-servers: localhost:9092
spring.kafka.consumer.group-id: group-id1
spring.kafka.consumer.auto-offset-reset: earliest
spring.kafka.consumer.key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
spring.kafka.consumer.properties.spring.json.trusted.packages=*

=======================================================================================

package com.app.jsonserializer.service;


import com.app.jsonserializer.config.AppConstants;
import com.app.jsonserializer.payload.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Service;

@Service
public class KafkaConsumer {




private static final Logger LOGGER = LoggerFactory.getLogger(KafkaConsumer.class);

@KafkaListener(topics = com.app.jsonserializer.config.AppConstants.JSON_SERIALIZER_TOPIC_NAME,
groupId = com.app.jsonserializer.config.AppConstants.GROUP_ID)
public void consume(com.app.jsonserializer.payload.User data){
LOGGER.info(String.format("Message received -> %s", data.toString()));
}


}




 

 Basic Concepts Apache Kafka:

1. Start Zookeeper
C:\ApacheKafka\kafka_2.12-3.5.0>bin\windows\zookeeper-server-start.bat config\zookeeper.properties
2. Start Kafka Server
C:\ApacheKafka\kafka_2.12-3.5.0>bin\windows\kafka-server-start.bat config\server.properties
3. How Topic Created?
C:\ApacheKafka\kafka_2.12-3.5.0>bin\windows\kafka-topics.bat --create --topic employee-topic --bootstrap-server localhost:9092
Created topic employee-topic.
4. How to produce the message ?
C:\ApacheKafka\kafka_2.12-3.5.0>bin\windows\kafka-console-producer.bat --topic employee-topic --bootstrap-server localhost:9092
5. How to consume the Message ?
C:\ApacheKafka\kafka_2.12-3.5.0>bin\windows\kafka-console-consumer.bat --topic employee-topic --from-beginning --bootstrap-server localhost:9092