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)