0% found this document useful (0 votes)
11 views

Design Patterns

Uploaded by

Digambar Gurav
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
11 views

Design Patterns

Uploaded by

Digambar Gurav
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 13

Understanding and applying design patterns in Springboot

Ngane Emmanuel

2024-10-23

1. Introduction to Design Patterns in Software Engineering

1.1 What are Design Patterns?

Design patterns are tried-and-tested solutions to common software design problems. They provide a reusable
and standard approach to solving recurring issues in software development. Think of them like a recipe—by
following the pattern, developers can efficiently solve problems with well-established practices.

1.2 History and Origins of Design Patterns

Design patterns originated from the book “Design Patterns: Elements of Reusable Object-Oriented Software”
(1994) by the “Gang of Four” (Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides). This book
categorized patterns into different groups, laying the foundation for modern software architecture practices.

1.3 Importance of Design Patterns in Modern Software Development

Design patterns help developers write cleaner, more maintainable code. They promote best practices, reduce
technical debt, and enable efficient communication among developers by providing a shared vocabulary for
solutions.

1.4 Why Use Design Patterns in Spring Boot?

Spring Boot simplifies the development of Java-based applications. However, with simplicity comes complex-
ity in scaling, maintaining, and managing dependencies. Design patterns in Spring Boot help: - Organize
code better (e.g., with Dependency Injection). - Enable reusable components (e.g., Singleton Beans). - Make
applications more robust and easier to maintain.

2. Classification of Design Patterns


Design patterns are grouped into three main categories:

1. Creational Patterns: Deal with object creation and initialization.


2. Structural Patterns: Organize objects and classes to form larger structures.
3. Behavioral Patterns: Manage communication between objects and responsibility assignment.

1
3. Creational Design Patterns in Spring Boot

3.1 Singleton Pattern

Overview and Concept

The Singleton pattern ensures a class has only one instance, providing a global access point to that instance.
In Spring Boot, this concept aligns with how beans are managed—by default, Spring beans are Singleton
scoped.

Code Example in Spring Boot (Bean Scopes)

@Component
public class AppConfig {
public AppConfig() {
System.out.println("Singleton Bean Created");
}
}

// Usage in a Controller
@RestController
public class HelloController {

private final AppConfig appConfig;

public HelloController(AppConfig appConfig) {


this.appConfig = appConfig;
}

@GetMapping("/hello")
public String sayHello() {
return "Hello from Singleton Bean!";
}
}

Real-Life Example: Configuration Management

Think of a traffic signal controller. There is only one signal control system for an intersection that all
lights depend on—this is similar to a Singleton bean where one instance manages traffic (or configuration)
across the entire system.

When to Use

• When you need a single instance for centralized management (e.g., configuration settings).
• For stateful services where only one instance must exist.

2
3.2 Factory Pattern

Concept and Variants

The Factory pattern provides an interface for creating objects, allowing subclasses to decide which class to
instantiate. It’s useful when the creation logic is complex or involves multiple options.
Variants include:

• Simple Factory
• Factory Method
• Abstract Factory

Spring Boot Example: Service Provider Selection

public interface PaymentService {


void processPayment();
}

@Component("PayPal")
public class PayPalService implements PaymentService {
public void processPayment() {
System.out.println("Processing payment via PayPal.");
}
}

@Component("Stripe")
public class StripeService implements PaymentService {
public void processPayment() {
System.out.println("Processing payment via Stripe.");
}
}

// Factory Class
@Component
public class PaymentFactory {

@Autowired
private ApplicationContext context;

public PaymentService getPaymentService(String serviceName) {


return (PaymentService) context.getBean(serviceName);
}
}

// Controller Usage
@RestController
public class PaymentController {

private final PaymentFactory paymentFactory;

public PaymentController(PaymentFactory paymentFactory) {

3
this.paymentFactory = paymentFactory;
}

@PostMapping("/pay/{service}")
public String processPayment(@PathVariable String service) {
PaymentService paymentService = paymentFactory.getPaymentService(service);
paymentService.processPayment();
return "Payment processed!";
}
}

Example Scenario

Think of online shopping platforms like Amazon. Depending on the selected payment method (PayPal,
Credit Card, or Stripe), the platform dynamically selects the appropriate service provider.

3.3 Builder Pattern

Overview and Code Implementation in Spring Boot

The Builder pattern helps construct complex objects step-by-step, ensuring readability and flexibility.

public class User {


private String username;
private String email;
private String role;

private User(Builder builder) {


this.username = builder.username;
this.email = builder.email;
this.role = builder.role;
}

public static class Builder {


private String username;
private String email;
private String role;

public Builder setUsername(String username) {


this.username = username;
return this;
}

public Builder setEmail(String email) {


this.email = email;
return this;
}

public Builder setRole(String role) {


this.role = role;
return this;
}

4
public User build() {
return new User(this);
}
}
}

// Usage
User user = new User.Builder()
.setUsername("johndoe")
.setEmail("[email protected]")
.setRole("Admin")
.build();

Scenario: Constructing Complex HTTP Requests

Think of constructing a pizza order. You add toppings, set the size, and choose the type of crust—one
step at a time. Similarly, the Builder pattern helps you build complex objects like user profiles or HTTP
requests.

4. Structural Design Patterns in Spring Boot

4.1 Adapter Pattern


Overview and Usage

The Adapter pattern allows incompatible interfaces to work together. In Spring Boot, this can be seen when
integrating third-party APIs into controllers.

Example: Integrating External APIs

@RestController
public class WeatherController {

@GetMapping("/weather/{city}")
public String getWeather(@PathVariable String city) {
// Adapter logic to convert external API response to internal format
return "Weather data for " + city;
}
}

4.2 Proxy Pattern

Overview and Concept


The Proxy pattern provides a surrogate or placeholder for another object to control access to it. It is
useful when objects are resource-intensive or require controlled access. In Spring Boot, this pattern is
commonly implemented using Aspect-Oriented Programming (AOP) for tasks such as logging, caching,
and security.

5
Code Example: Logging and Caching with Proxies

@Aspect
@Component
public class LoggingAspect {

@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Executing: " + joinPoint.getSignature().getName());
}

@Around("execution(* com.example.service.*.*(..))")
public Object cacheResult(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Checking cache before execution...");
// Simulated caching logic
Object result = joinPoint.proceed();
System.out.println("Storing result in cache...");
return result;
}
}

Real-Life Example: API Rate Limiting

Think of an airport check-in desk. Before passengers are granted boarding passes, staff check their
documents—this process acts like a proxy to ensure only authorized individuals proceed to boarding. Simi-
larly, a proxy limits or controls access to resources.

When to Use

• To manage access to expensive resources (e.g., external services).


• For cross-cutting concerns such as logging and authentication.

4.3 Decorator Pattern

Overview and Use Case

The Decorator pattern allows behavior to be added to individual objects dynamically without altering the
behavior of other objects. It’s often used to enhance the functionality of objects.

Code Example: Adding Features to Beans Dynamically

public interface Notifier {


void send(String message);
}

@Component
public class EmailNotifier implements Notifier {
public void send(String message) {

6
System.out.println("Sending email: " + message);
}
}

@Component
public class SlackNotifierDecorator implements Notifier {

private final Notifier wrapped;

@Autowired
public SlackNotifierDecorator(EmailNotifier emailNotifier) {
this.wrapped = emailNotifier;
}

@Override
public void send(String message) {
wrapped.send(message);
System.out.println("Sending Slack notification: " + message);
}
}

// Usage in a Controller
@RestController
public class NotificationController {

private final Notifier notifier;

@Autowired
public NotificationController(SlackNotifierDecorator notifier) {
this.notifier = notifier;
}

@PostMapping("/notify")
public String notify(@RequestBody String message) {
notifier.send(message);
return "Notification sent!";
}
}

Scenario: Multi-Channel Notifications

Imagine sending notifications via both email and Slack. Instead of rewriting the notification logic, you
decorate the original EmailNotifier with additional functionality to send Slack messages, too.

7
5. Behavioral Design Patterns in Spring Boot

5.1 Observer Pattern

Concept and Code Example with Spring Events

The Observer pattern allows multiple objects (observers) to react to changes in the state of another object
(subject). In Spring Boot, it is implemented using Application Events.

@Component
public class UserRegistrationListener {

@EventListener
public void onUserRegistration(UserRegisteredEvent event) {
System.out.println("User registered: " + event.getUser().getName());
}
}

public class UserRegisteredEvent extends ApplicationEvent {


private final User user;

public UserRegisteredEvent(User user) {


super(user);
this.user = user;
}

public User getUser() {


return user;
}
}

// Controller to Trigger the Event


@RestController
public class UserController {

private final ApplicationEventPublisher publisher;

@Autowired
public UserController(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}

@PostMapping("/register")
public String registerUser(@RequestBody User user) {
publisher.publishEvent(new UserRegisteredEvent(user));
return "User registered!";
}
}

Scenario: User Notification System

Imagine a social media platform where users receive notifications when a friend posts an update. The
Observer pattern ensures that all interested parties are notified when an event (like registration) occurs.

8
5.2 Template Method Pattern

Overview and Example in Spring’s JdbcTemplate

The Template Method pattern defines the structure of an algorithm in a base class while allowing subclasses
to modify parts of the algorithm. Spring’s JdbcTemplate is a great example, as it provides a template for
database operations while leaving specific SQL details to be provided by the developer.

@Repository
public class UserRepository {

private final JdbcTemplate jdbcTemplate;

@Autowired
public UserRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

public void saveUser(User user) {


String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
jdbcTemplate.update(sql, user.getName(), user.getEmail());
}
}

Scenario: Streamlining Database Operations

Think of a fast-food restaurant. The cooking process is standardized (template), but customers can
choose their toppings (custom behavior). Similarly, the JdbcTemplate standardizes database operations
while allowing customization.

5.3 Command Pattern

Concept and Implementation with Command Objects

The Command pattern encapsulates a request as an object, allowing you to parameterize clients with different
requests and support undoable operations.

public interface Command {


void execute();
}

@Component
public class LightOnCommand implements Command {
@Override
public void execute() {
System.out.println("Light turned on.");
}
}

@Component
public class LightOffCommand implements Command {
@Override

9
public void execute() {
System.out.println("Light turned off.");
}
}

// Controller for Triggering Commands


@RestController
public class LightController {

private final Command onCommand;


private final Command offCommand;

@Autowired
public LightController(LightOnCommand onCommand, LightOffCommand offCommand) {
this.onCommand = onCommand;
this.offCommand = offCommand;
}

@PostMapping("/light/{action}")
public String controlLight(@PathVariable String action) {
if ("on".equals(action)) {
onCommand.execute();
} else {
offCommand.execute();
}
return "Light " + action + "!";
}
}

Scenario: Implementing Undo/Redo in Spring Boot Apps

Think of a TV remote where each button press triggers a command. Using the Command pattern, you
can even implement undo/redo functionality—just like canceling a previous action.

6. Non-Functional Requirements Supported by Design Patterns in


Spring Boot

6.1 Scalability

Design patterns such as the Singleton and Microservice architecture help manage shared resources
efficiently, allowing the system to scale horizontally. For instance, in a Spring Boot microservice environment,
breaking applications into small services increases scalability as each service can scale independently.

6.2 Maintainability

Patterns like Template Method and Factory promote reusable and consistent code structures. This makes
the code easy to maintain over time, as developers follow a standard practice for implementation.
Example: If all database operations use JdbcTemplate, adding or changing the underlying logic requires
minimal effort, making maintenance faster.

10
6.3 Performance Optimization

Patterns such as Proxy and Decorator can improve performance by implementing caching and lazy loading.
In Spring Boot, proxies can intercept expensive service calls and return cached results when applicable.
Example: Caching service responses through proxies reduces repeated database access, thereby improving
performance.

6.4 Security

The Proxy pattern is also useful for implementing security aspects, such as validating user credentials
before allowing access to critical resources. Singletons can enforce a single authentication mechanism
across the application.

6.5 Usability and Flexibility

Behavioral patterns like Observer and Command make applications more user-friendly by adding real-
time responsiveness and undo/redo functionality. This makes the application adaptable to changing user
needs.

7. Advantages and Disadvantages of Using Design Patterns in


Spring Boot

7.1 Advantages

1. Reusability: Design patterns promote the reuse of solutions for common software challenges, reducing
development time.
2. Consistency: They ensure that developers follow a standard way of solving problems, resulting in
consistent and predictable code.
3. Extensibility: Patterns like Decorator allow you to extend functionality without altering existing
code.
4. Separation of Concerns: Architectural patterns like Microservice separate logic into independent
units, promoting modularity and ease of debugging.
5. Testability: Using patterns like Command makes it easier to test individual commands indepen-
dently.

7.2 Disadvantages

1. Overhead: Applying patterns where they aren’t needed can lead to unnecessary complexity.
2. Learning Curve: Understanding and correctly implementing patterns requires experience and knowl-
edge.
3. Over-engineering Risk: Patterns might introduce excessive abstractions if used incorrectly, compli-
cating code unnecessarily.
4. Performance Trade-offs: Some patterns, such as Observer, can introduce latency due to the
overhead of notifying multiple observers.

11
8. Practical Scenarios for Applying Design Patterns in Spring Boot

8.1 E-Commerce Application (Observer Pattern)

In an e-commerce platform, notifications are sent to users when new products arrive or when their order
status changes. The Observer pattern ensures that all relevant parties (customers, suppliers) receive
updates automatically.

8.2 Banking System (Template Method Pattern)

A banking application follows specific steps for loan processing—like credit checks and document validation—
while allowing customization of specific logic. The Template Method pattern ensures that the overall
flow is consistent across different loan types.

8.3 IoT Applications (Command Pattern)

In IoT environments, actions like turning lights on or off are triggered by specific commands. With the
Command pattern, you can implement undo/redo functionality, such as switching appliances back to
their previous state in case of an error.

8.4 Social Media Application (Proxy Pattern)

In a social media app, the Proxy pattern is used to limit the rate of API calls, ensuring fair usage and
avoiding server overload. Proxies also manage authentication for accessing user profiles.

9. Best Practices for Implementing Design Patterns in Spring Boot


1. Understand the Problem Domain: Always assess if the problem you’re solving fits the pattern
before applying it. Avoid over-engineering.
2. Use Annotations for Simplicity: Spring Boot annotations (like @Component, @Autowired) help in
implementing patterns with minimal code.
3. Combine Patterns Where Necessary: Some problems might require multiple patterns working
together (e.g., Factory + Singleton for managing instances).
4. Test Thoroughly: Use unit testing to verify that patterns are implemented correctly, especially when
dealing with behavioral patterns like Command or Observer.
5. Document Your Code: Ensure that your implementation is well-documented to help other devel-
opers understand the patterns in use.

10. Conclusion
Design patterns are essential tools in software development, helping developers build scalable, maintainable,
and reliable applications. In the context of Spring Boot, patterns like Singleton, Proxy, Observer, and
Template Method are commonly applied to address various challenges such as performance optimization,
modularization, and user experience. While these patterns offer numerous advantages, it is crucial to use
them wisely to avoid over-complicating solutions. With proper application, design patterns not only improve
the quality of code but also contribute to the overall success of a project.

12
11. References
1. Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1994). Design Patterns: Elements of
Reusable Object-Oriented Software. Addison-Wesley.
2. Craig Walls. (2016). Spring in Action, 5th Edition. Manning Publications.
3. Rod Johnson. (2014). Expert One-on-One J2EE Development without EJB. Wrox.

13

You might also like