Design patterns in Java are reusable solutions to common software design problems. They help developers write more efficient, maintainable, and scalable code. Let's explore a few design patterns with simple examples.
1. Singleton Pattern
Purpose: Ensures that a class has only one instance and provides a global point of access to it.
Example: Database connection manager.
class DatabaseConnection {
    private static DatabaseConnection instance;
    private DatabaseConnection() {
        // Private constructor to prevent instantiation
        System.out.println("Database Connection Created!");
    }
    public static DatabaseConnection getInstance() {
        if (instance == null) {
            instance = new DatabaseConnection();
        }
        return instance;
    }
}
public class SingletonExample {
    public static void main(String[] args) {
        DatabaseConnection db1 = DatabaseConnection.getInstance();
        DatabaseConnection db2 = DatabaseConnection.getInstance();
        System.out.println(db1 == db2); // Output: true
    }
}
2. Factory Pattern
Purpose: Creates objects without exposing the instantiation logic to the client.
Example: Shape factory.
interface Shape {
    void draw();
}
class Circle implements Shape {
    public void draw() {
        System.out.println("Drawing Circle");
    }
}
class Square implements Shape {
    public void draw() {
        System.out.println("Drawing Square");
    }
}
class ShapeFactory {
    public static Shape getShape(String shapeType) {
        if ("CIRCLE".equalsIgnoreCase(shapeType)) {
            return new Circle();
        } else if ("SQUARE".equalsIgnoreCase(shapeType)) {
            return new Square();
        }
        return null;
    }
}
public class FactoryExample {
    public static void main(String[] args) {
        Shape circle = ShapeFactory.getShape("CIRCLE");
        circle.draw();
        Shape square = ShapeFactory.getShape("SQUARE");
        square.draw();
    }
}
3. Observer Pattern
Purpose: Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified.
Example: Weather station.
import java.util.ArrayList;
import java.util.List;
// Observer interface
interface Observer {
    void update(float temperature);
}
// Subject class
class WeatherStation {
    private List<Observer> observers = new ArrayList<>();
    private float temperature;
    public void addObserver(Observer observer) {
        observers.add(observer);
    }
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }
    public void setTemperature(float temperature) {
        this.temperature = temperature;
        notifyObservers();
    }
    private void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(temperature);
        }
    }
}
// Concrete Observer
class PhoneDisplay implements Observer {
    @Override
    public void update(float temperature) {
        System.out.println("Phone Display: Temperature updated to " + temperature);
    }
}
// Concrete Observer
class WebDisplay implements Observer {
    @Override
    public void update(float temperature) {
        System.out.println("Web Display: Temperature updated to " + temperature);
    }
}
public class ObserverExample {
    public static void main(String[] args) {
        WeatherStation station = new WeatherStation();
        Observer phoneDisplay = new PhoneDisplay();
        Observer webDisplay = new WebDisplay();
        station.addObserver(phoneDisplay);
        station.addObserver(webDisplay);
        station.setTemperature(25.5f);
        station.setTemperature(30.0f);
    }
}
4. Strategy Pattern
Purpose: Defines a family of algorithms, encapsulates each one, and makes them interchangeable.
Example: Payment system.
interface PaymentStrategy {
    void pay(int amount);
}
class CreditCardPayment implements PaymentStrategy {
    public void pay(int amount) {
        System.out.println("Paid " + amount + " using Credit Card");
    }
}
class PayPalPayment implements PaymentStrategy {
    public void pay(int amount) {
        System.out.println("Paid " + amount + " using PayPal");
    }
}
class ShoppingCart {
    private PaymentStrategy paymentStrategy;
    public ShoppingCart(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }
    public void checkout(int amount) {
        paymentStrategy.pay(amount);
    }
}
public class StrategyExample {
    public static void main(String[] args) {
        ShoppingCart cart1 = new ShoppingCart(new CreditCardPayment());
        cart1.checkout(100);
        ShoppingCart cart2 = new ShoppingCart(new PayPalPayment());
        cart2.checkout(200);
    }
}
5. Decorator Pattern
Purpose: Dynamically adds new behavior to an object.
Example: Adding features to a coffee order.
interface Coffee {
    String getDescription();
    double getCost();
}
class BasicCoffee implements Coffee {
    public String getDescription() {
        return "Basic Coffee";
    }
    public double getCost() {
        return 5.0;
    }
}
class MilkDecorator implements Coffee {
    private Coffee coffee;
    public MilkDecorator(Coffee coffee) {
        this.coffee = coffee;
    }
    public String getDescription() {
        return coffee.getDescription() + ", Milk";
    }
    public double getCost() {
        return coffee.getCost() + 1.0;
    }
}
class SugarDecorator implements Coffee {
    private Coffee coffee;
    public SugarDecorator(Coffee coffee) {
        this.coffee = coffee;
    }
    public String getDescription() {
        return coffee.getDescription() + ", Sugar";
    }
    public double getCost() {
        return coffee.getCost() + 0.5;
    }
}
public class DecoratorExample {
    public static void main(String[] args) {
        Coffee coffee = new BasicCoffee();
        coffee = new MilkDecorator(coffee);
        coffee = new SugarDecorator(coffee);
        System.out.println(coffee.getDescription() + ": $" + coffee.getCost());
    }
}
    
Top comments (0)