Design Pattern
SOLID pricnciple
1)Strategy Design Pattern
📝 Strategy Design Pattern - Notes
The Strategy Design Pattern is a behavioral design pattern that enables flexibility in how algorithms or behaviors are
executed. It allows you to define a family of algorithms, encapsulate each one, and make them interchangeable. The
Strategy pattern helps in selecting an algorithm at runtime without altering the client code.
Key Components:
1.
Strategy Interface
o
Defines a common interface for all algorithms/strategies.
o
Clients use this interface to interact with different algorithms without needing to know the
specifics.
Example:
java
CopyEdit
public interface SendStrategy {
void send(String message);
}
2.
Concrete Strategy
o
Implements the Strategy interface with a specific algorithm/behavior.
Example:
java
CopyEdit
public class EmailSendStrategy implements SendStrategy {
@Override
public void send(String message) {
System.out.println("Sending EMAIL: " + message);
}
}
public class SmsSendStrategy implements SendStrategy {
@Override
public void send(String message) {
System.out.println("Sending SMS: " + message);
}
}
3.
Context
o
Maintains a reference to a Strategy object and delegates the request to the current strategy.
o
The Context class does not need to know the details of the algorithm, only the strategy interface.
Example:
java
CopyEdit
public class Notification {
private SendStrategy strategy;
public Notification(SendStrategy strategy) {
this.strategy = strategy;
}
public void send(String message) {
strategy.send(message);
}
}
4.
Client
o
The client sets the strategy at runtime, often using a factory or direct assignment.
How Strategy Pattern Works:
Instead of implementing an algorithm directly inside a class, you define multiple strategies (algorithms)
and encapsulate them.
The client can choose which strategy to use at runtime (e.g., via configuration, factory, etc.).
You avoid hard-coding the logic inside a class, enabling easy extensions and swapping of behaviors.
Benefits:
1.
Open/Closed Principle
o
You can add new strategies without modifying existing code, ensuring the code is closed for
modification but open for extension.
2.
Decoupling
o
The Context class is decoupled from the specific algorithms, making it easy to introduce new ones
without affecting the rest of the system.
3.
Runtime Behavior Switching
o
You can change the strategy dynamically at runtime based on specific conditions or
configurations.
4.
Maintainability and Reusability
o
Strategies are separate, so they are easier to maintain, test, and reuse across different contexts.
When to Use Strategy Pattern:
When you have multiple algorithms to solve a problem, but you want to make them interchangeable and
decouple them from the client code.
When your code has conditional logic to choose between different algorithms. You can replace those
conditionals with strategy classes.
When your algorithm is likely to change frequently or needs to vary for different clients or environments.
Example Scenario (Notification System)
Without Strategy Pattern (Inheritance-based approach)
You would have to create subclasses for each notification type (EmailNotification, SmsNotification, etc.)
and implement the logic for sending notifications within those subclasses.
If you wanted to add a new notification type (like PushNotification), you'd have to modify existing code or
add more subclasses.
With Strategy Pattern (Composition-based approach)
SendStrategy interface defines a common interface for sending messages.
Concrete
strategies
implement
the
interface
(e.g.,
EmailSendStrategy,
SmsSendStrategy,
PushSendStrategy).
The Notification class uses a strategy object, allowing it to delegate the task of sending messages without
knowing the specific implementation.
Code Example:
java
CopyEdit
// Strategy Interface
public interface SendStrategy {
void send(String message);
}
// Concrete Strategies
public class EmailSendStrategy implements SendStrategy {
public void send(String message) {
System.out.println("Sending EMAIL: " + message);
}
}
public class SmsSendStrategy implements SendStrategy {
public void send(String message) {
System.out.println("Sending SMS: " + message);
}
}
public class PushSendStrategy implements SendStrategy {
public void send(String message) {
System.out.println("Sending PUSH notification: " + message);
}
}
// Context Class
public class Notification {
private SendStrategy strategy;
public Notification(SendStrategy strategy) {
this.strategy = strategy;
}
public void send(String message) {
strategy.send(message);
}
}
// Client Code (Main)
public class Main {
public static void main(String[] args) {
SendStrategy strategy = new EmailSendStrategy(); // Strategy selection
Notification notification = new Notification(strategy);
notification.send("Hello, this is an email!");
strategy = new SmsSendStrategy(); // Strategy change at runtime
notification = new Notification(strategy);
notification.send("Hello, this is an SMS!");
strategy = new PushSendStrategy(); // Another strategy change
notification = new Notification(strategy);
notification.send("Hello, this is a Push notification!");
}
}
Real-World Example:
A payment gateway system can use the Strategy Pattern to select the appropriate payment strategy (e.g., PayPal,
Credit Card, Apple Pay, etc.) without modifying the payment processing class itself. The client simply selects the
strategy (payment method) dynamically at runtime.
Drawbacks:
Overhead: Sometimes, using too many strategies may create unnecessary complexity in simpler systems.
Dependency Injection: In some cases, injecting the strategy might require complex configuration
management (e.g., using Spring or other DI frameworks).
Summary:
The Strategy Pattern allows you to define a family of algorithms and make them interchangeable.
It decouples the context from the behavior, making the system more flexible and extensible.
The strategy is chosen dynamically at runtime, following the Open/Closed Principle, meaning that the
system can be extended without modifying existing code.
When to use: Multiple related algorithms, conditional logic, dynamic behavior changes.
� Strategy Design Pattern – Notes
🔷 Definition:
Strategy Pattern is a behavioral design pattern that lets you define a family of algorithms (strategies), put each of them in a
separate class, and make them interchangeable at runtime.
🔷 Intent:
To enable selecting an algorithm’s behavior at runtime.
To eliminate conditional logic like if-else or switch for behavior variation.
To promote Open/Closed Principle – open for extension, closed for modification.
🔷 Key Components:
Component
Role
Strategy
Interface that defines the behavior.
ConcreteStrategy Different implementations of the behavior.
Context
Uses a Strategy to execute the behavior; it is configurable.
🔷 Common Use Cases:
Payment processing (credit card, UPI, wallet).
Notification sending (SMS, email, push).
Sorting algorithms (bubble sort, quick sort).
Compression algorithms (ZIP, RAR).
✅ Clarification: Code Duplication and Strategy Pattern
❓ Doubt:
Is the Strategy Pattern used to remove duplicate code in subclasses?
✅ Clarification:
Not the primary purpose, but yes, the Strategy Pattern can eliminate duplicate code that occurs when subclasses
implement similar behaviors.
Instead of having child classes overriding a method with similar logic, you move that logic into strategy classes,
making it configurable and reusable.
🔁 Before (with duplication):
java
CopyEdit
abstract class Notification {
abstract void send(String message);
}
class EmailNotification extends Notification {
void send(String message) {
// duplicate email logic
}
}
class SmsNotification extends Notification {
void send(String message) {
// duplicate SMS logic
}
}
✅ After (Strategy Pattern):
java
CopyEdit
interface SendStrategy {
void send(String message);
}
class EmailSendStrategy implements SendStrategy {
public void send(String message) {
System.out.println("Sending EMAIL: " + message);
}
}
class Notification {
private SendStrategy strategy;
public Notification(SendStrategy strategy) {
this.strategy = strategy;
}
public void send(String message) {
strategy.send(message);
}
}
Result: No more duplicated send() methods in subclasses. Logic is moved to interchangeable strategy classes.
🔷 Advantages:
Eliminates code duplication across child classes.
Replaces if-else/switch statements with polymorphism.
Promotes composition over inheritance.
Makes algorithms or behaviors interchangeable without touching the Context.
🔷 Disadvantages:
Increases number of classes.
Requires careful runtime configuration of the correct strategy.
Behavior becomes harder to trace if overused.
🔷 When to Use:
When multiple classes perform similar tasks with only minor differences.
When you have lots of conditional logic based on object type or configuration.
When behavior needs to change at runtime.
✅ Real-Life Analogy:
Think of a navigation app:
Strategy = Route Calculation Algorithm
Concrete Strategies = Driving, Walking, Bicycling, Transit
Context = Google Maps app that chooses the strategy based on user input
📝 Summary Table:
Concept
Description
Pattern Type
Behavioral
Purpose
Encapsulate interchangeable behaviors
Helps Remove Duplicated logic in subclasses or conditional blocks
Promotes
Open/Closed Principle, composition over inheritance
Downside
More classes to manage
📘
Observer Design Pattern
– Notes
🔍 Definition
The Observer Pattern is a behavioral design pattern where one object (Subject) notifies a list of other objects (Observers)
about changes in its state, without knowing who they are.
🎯 Intent
To define a one-to-many dependency so that when one object changes, all its dependents (observers) are automatically
notified and updated.
� Key Components
Component
Role
Subject
The object that holds the state and notifies observers
Observer
Interface for objects that want to receive updates
ConcreteSubject
Real object being observed, tracks and notifies observers
ConcreteObserver Real object that receives updates from subject
🔁 Workflow
1.
Observers register (subscribe) with the subject.
2.
Subject holds a list of registered observers.
3.
Subject's state changes.
4.
Subject calls notifyObservers().
5.
Each observer receives an update() call.
✅ Benefits
Promotes loose coupling between subject and observers
Enables dynamic runtime subscriptions
Follows Open/Closed Principle: Add new observers without modifying subject
❌ Drawbacks
Memory leaks if observers aren’t removed properly
Notification cascade: one update can trigger many others
Harder debugging with many observers
📊 Push vs Pull Models
Model Description
Push
Subject sends data directly to observers
Pull
Observers are notified and then pull data from the subject
� 💻 Java Example
Observer.java
java
CopyEdit
public interface Observer {
void update(String message);
}
Subject.java
java
CopyEdit
public interface Subject {
void attach(Observer o);
void detach(Observer o);
void notifyObservers(String message);
}
ConcreteSubject.java
java
CopyEdit
public class NewsAgency implements Subject {
private List<Observer> observers = new ArrayList<>();
public void attach(Observer o) { observers.add(o); }
public void detach(Observer o) { observers.remove(o); }
public void notifyObservers(String news) {
for (Observer o : observers) {
o.update(news);
}
}
}
ConcreteObserver.java
java
CopyEdit
public class EmailSubscriber implements Observer {
public void update(String message) {
System.out.println("Email received: " + message);
}
}
Main.java
java
CopyEdit
public class Main {
public static void main(String[] args) {
NewsAgency agency = new NewsAgency();
Observer email = new EmailSubscriber();
agency.attach(email);
agency.notifyObservers("Breaking News!");
}
}
🏗 Use Cases
UI frameworks (e.g. button click listeners)
Chat or notification systems
Event-driven architectures
Stock tickers or weather data
Model-View-Controller (MVC) – View observes Model
� Design Principles Used
Loose Coupling – Subject and Observer don’t depend on each other’s implementation
Open/Closed Principle – Add observers without changing the subject
Single Responsibility – Each class has a distinct role (observer or subject)
🔄 Observer vs Pub/Sub
Aspect
Observer Pattern
Publish-Subscribe (Pub/Sub)
Communication Direct
Through event/message broker
Coupling
Tightly coupled
Loosely coupled
Scope
Within same process
Can be across systems
Examples
Java/Swing events, MVC Kafka, Redis Pub/Sub, RabbitMQ
✅ Summary
Use when multiple objects need to stay in sync with one subject.
Encourages decoupled, event-driven design.
Use carefully in systems with many observers to avoid complexity.
You said:
if the observer is subjected to many subject and what is other method to get the changed data
ChatGPT said:
Great question! When an observer is observing multiple subjects, things can get more complex, especially when it needs to
know which subject triggered the update or what exactly changed.
Let's break it down:
👥 Observer Watching Multiple Subjects
❓ The Problem
An observer (say UserDashboard) might be observing:
WeatherService
StockMarketService
NewsFeedService
When update() is called, the observer doesn't automatically know:
Which subject changed?
What part of the data changed?
✅ Two Common Solutions
✅ 1. Pass the Subject to the Observer (Pull Model)
Modify the update() method to accept a reference to the subject:
java
CopyEdit
interface Observer {
void update(Subject subject); // pass the subject
}
Why?
Now the observer can call getState() or other methods on the subject to pull the latest data and also identify which subject
triggered the update.
Example:
java
CopyEdit
public void update(Subject subject) {
if (subject instanceof WeatherService) {
System.out.println("Weather updated: " + ((WeatherService) subject).getTemperature());
} else if (subject instanceof StockMarketService) {
System.out.println("Stock updated: " + ((StockMarketService) subject).getIndex());
}
}
✅ 2. Push Model: Pass the Data + Subject Info
You can extend the update() method to receive:
java
CopyEdit
void update(Subject subject, Object data);
Or, even better, create a ChangeEvent object:
java
CopyEdit
class ChangeEvent {
Subject source;
String changeType;
Object payload;
}
Then observer receives:
java
CopyEdit
void update(ChangeEvent event);
This allows you to:
Know the source
Understand the type of change
Access the changed data directly
� Summary of the Two Models
Model Method Signature
Behavior
Pull
update(Subject subject)
Observer queries subject
Push
update(Subject subject, data) Subject sends relevant data
Event update(ChangeEvent event)
More structured & flexible push
🔐 Best Practices
Use Pull model when observers need flexibility to decide what to fetch.
Use Push model or Event object when:
o
The data is heavy or expensive to pull.
o
You want to avoid casting or type-checking.
ChatGPT said:
Excellent observation. What you're referring to is a design enhancement for the Observer pattern when an observer is
interested in only one subject, or you want to establish a tight, clearly defined relationship between a ConcreteObserver
and a specific Subject instance.
✅ Idea: Constructor Injection of Subject into Observer
Instead of having a generic update() method and checking which subject triggered it, you can directly inject the subject the
observer is meant to observe.
🔄 What Changes?
You create a tight 1-to-1 relationship between a ConcreteObserver and its subject using constructor injection:
� Example
java
CopyEdit
// Subject interface
interface Subject {
void attach(Observer o);
void detach(Observer o);
void notifyObservers();
}
// Observer interface
interface Observer {
void update(); // no need to pass subject, already have it via injection
}
� 💻 Concrete Subject
java
CopyEdit
class WeatherService implements Subject {
private List<Observer> observers = new ArrayList<>();
private int temperature;
public void setTemperature(int temp) {
this.temperature = temp;
notifyObservers();
}
public int getTemperature() {
return temperature;
}
public void attach(Observer o) { observers.add(o); }
public void detach(Observer o) { observers.remove(o); }
public void notifyObservers() {
for (Observer o : observers) {
o.update(); // No need to pass `this`
}
}
}
👁️ Concrete Observer With Constructor Injection
java
CopyEdit
class WeatherDisplay implements Observer {
private final WeatherService weatherService;
public WeatherDisplay(WeatherService weatherService) {
this.weatherService = weatherService;
this.weatherService.attach(this); // Register itself
}
public void update() {
System.out.println("Updated temperature: " + weatherService.getTemperature());
}
}
🏁 Main
java
CopyEdit
public class Main {
public static void main(String[] args) {
WeatherService service = new WeatherService();
WeatherDisplay display = new WeatherDisplay(service); // injection
service.setTemperature(30);
service.setTemperature(35);
}
}
✅ Benefits
Advantage
Explanation
✔ Cleaner code
No need for update(Subject) or instanceof checks
✔ Tight binding if needed When observer is always tied to a specific subject
✔ Easier to unit test
Can mock/inject subject easily
✔ Better for small systems Especially where you know dependencies upfront
❗ Trade-offs
Concern
Why it matters
❌ Less flexible
Observer is now tightly coupled to subject
❌ Harder for multiple subjects
You need separate observer classes
❌ Not ideal for generic frameworks Less reusable in dynamic systems
💬 Conclusion
So yes, when you:
Want a concrete observer to always observe a specific subject
Prefer clean dependency injection (e.g., in Spring)
Don’t want to identify subjects manually in update()
→ Then constructor injection with a direct reference is a very valid and clean approach.
Decorator pattern
The Decorator Pattern is a structural design pattern that allows behavior to be added to an individual object, dynamically,
without modifying the class itself. It follows the principle of composition over inheritance.
💡 Real-World Analogy
Think of a coffee shop. You start with a plain coffee, and you can decorate it with milk, sugar, whipped cream, etc., without
modifying the original coffee class. Each addition is a decorator.
✅ When to Use the Decorator Pattern
You want to add responsibilities to objects dynamically.
You want to avoid subclass explosion from many combinations of behaviors.
You want to follow the Open/Closed Principle (open for extension, closed for modification).
� Structure
text
CopyEdit
Component (interface)
↑
ConcreteComponent (core class)
↑
Decorator (abstract)
↑
ConcreteDecorators (add behavior)
☕ Example: Coffee with Decorators
1. Component Interface
java
CopyEdit
public interface Coffee {
String getDescription();
double getCost();
}
2. Concrete Component
java
CopyEdit
public class BasicCoffee implements Coffee {
public String getDescription() {
return "Basic Coffee";
}
public double getCost() {
return 5.0;
}
}
3. Abstract Decorator
java
CopyEdit
public abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
}
4. Concrete Decorators
java
CopyEdit
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
public double getCost() {
return coffee.getCost() + 1.5;
}
}
public class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return coffee.getDescription() + ", Sugar";
}
public double getCost() {
return coffee.getCost() + 0.5;
}
}
5. Usage
java
CopyEdit
public class Main {
public static void main(String[] args) {
Coffee coffee = new BasicCoffee();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
System.out.println(coffee.getDescription()); // Basic Coffee, Milk, Sugar
System.out.println("Cost: $" + coffee.getCost()); // Cost: $7.0
}
}
🆚 Decorator vs Inheritance
Inheritance
Decorator Pattern
Static (compile-time)
Dynamic (runtime)
Modifies entire class
Modifies one instance
Deep hierarchy
Flexible combinations
Hard to scale with variations Easy to extend behavior dynamically
🔚 Summary
Decorator lets you wrap an object to extend its behavior without changing its code.
Follows Open/Closed Principle.
Useful when you need to combine multiple optional behaviors dynamically.
You said:
Coffee coffee = new BasicCoffee(); coffee = new MilkDecorator(coffee); coffee = new SugarDecorator(coffee);
System.out.println(coffee.getDescription()); // Basic Coffee, Milk, Sugar System.out.println("Cost: $" + coffee.getCost()); //
Cost: $7.0 how this will run
ChatGPT said:
Great question! Let's walk through exactly how this code runs, line by line, so you understand the Decorator Pattern in
action.
Code:
java
CopyEdit
Coffee coffee = new BasicCoffee();
// Line 1
coffee = new MilkDecorator(coffee);
// Line 2
coffee = new SugarDecorator(coffee);
// Line 3
System.out.println(coffee.getDescription());
// Line 4
System.out.println("Cost: $" + coffee.getCost());
// Line 5
Step-by-Step Execution:
✅ Line 1:
java
CopyEdit
Coffee coffee = new BasicCoffee();
coffee now refers to an instance of BasicCoffee.
getDescription() → returns "Basic Coffee"
getCost() → returns 5.0
✅ Line 2:
java
CopyEdit
coffee = new MilkDecorator(coffee);
Now coffee is an instance of MilkDecorator.
Internally, MilkDecorator wraps the original BasicCoffee object.
getDescription() → returns coffee.getDescription() + ", Milk"
o
coffee here is the wrapped BasicCoffee, so this resolves to:
"Basic Coffee" + ", Milk" → "Basic Coffee, Milk"
getCost() → returns coffee.getCost() + 1.5
o
coffee.getCost() is 5.0, so total is 6.5
✅ Line 3:
java
CopyEdit
coffee = new SugarDecorator(coffee);
Now coffee is an instance of SugarDecorator.
Internally, SugarDecorator wraps the MilkDecorator, which wraps BasicCoffee.
getDescription() → returns:
java
CopyEdit
coffee.getDescription() + ", Sugar"
Resolves as:
o
MilkDecorator.getDescription() → "Basic Coffee, Milk"
o
Add Sugar → "Basic Coffee, Milk, Sugar"
getCost() → returns:
✅ Output:
o
MilkDecorator.getCost() → 6.5
o
Add Sugar → 6.5 + 0.5 = 7.0
java
CopyEdit
System.out.println(coffee.getDescription());
// → "Basic Coffee, Milk, Sugar"
System.out.println("Cost: $" + coffee.getCost());
// → "Cost: $7.0"
🔁 Decorator Chain Structure (Internally):
SugarDecorator
└── wraps → MilkDecorator
└── wraps → BasicCoffee
Each decorator calls the method of the object it wraps, then adds its own behavior.
� Summary:
Each decorator layer wraps the previous one.
Each getDescription() and getCost() delegates to the wrapped object and adds its own part.
The final object is like a chain of decorators, enhancing the core object (BasicCoffee).
You said:
why the super method is needed as the object is created in main class
ChatGPT said:
Excellent question!
You're asking:
Why do we call super(coffee) in the decorator class constructor, if we are already passing the coffee object in the main
class?
This is a key part of understanding composition and delegation in the Decorator Pattern.
✅ The Purpose of super(coffee) (or storing coffee in the decorator constructor)
Let’s look at the decorator base class:
java
CopyEdit
public abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
}
🤔 Why do we do this?
Even though the object (coffee) is created in the main method, we still need to pass it to the decorator so that the
decorator can delegate method calls to it.
The decorator needs to wrap and forward method calls to the original object (coffee) to preserve behavior.
🔍 Real Example:
java
CopyEdit
Coffee coffee = new BasicCoffee();
// Step 1
coffee = new MilkDecorator(coffee);
// Step 2
coffee = new SugarDecorator(coffee);
// Step 3
At Step 2:
You are passing the BasicCoffee object to the MilkDecorator.
Inside MilkDecorator, we call super(coffee) to store that object.
This allows MilkDecorator to do:
java
CopyEdit
return coffee.getDescription() + ", Milk";
If you don't store coffee (via constructor or super()), the decorator would have no reference to the original behavior, and
delegation would break.
🎯 Summary
The super(coffee) call in the decorator's constructor (or this.coffee = coffee) is necessary to store a reference to
the object being decorated.
This is what allows the decorator to:
o
Enhance the original object.
o
Chain multiple decorators.
o
Avoid rewriting logic (just delegate + add).
Even if the object is created in the main method, decorators still need it internally to perform composition-based
delegation.
Here are some real-time, real-world examples of the Decorator Pattern used in practice. These examples help you
understand when and why you’d use it beyond textbook Coffee cases.
✅ 1. Java I/O Streams (Classic Example)
java
CopyEdit
InputStream input = new FileInputStream("data.txt");
input = new BufferedInputStream(input);
input = new GZIPInputStream(input);
Explanation:
FileInputStream reads from file.
BufferedInputStream adds buffering (performance boost).
GZIPInputStream adds decompression.
👉 All these wrap the original stream and add behavior dynamically.
✅ 2. Spring Framework – Bean Wrappers & Proxies
Spring often wraps beans with proxies to add behavior like:
Transaction Management
Caching
Security
Logging
java
CopyEdit
@Transactional
public void transferMoney() {
// Spring wraps this method to add transaction behavior
}
🔍 Behind the scenes: Spring uses dynamic proxies (decorators) to wrap the original object and add behavior like
starting/committing transactions.
✅ 3. Logging in Middleware (e.g., Express, Filters)
In web servers or frameworks like Spring Boot or Express.js, each middleware layer acts like a decorator:
java
CopyEdit
app.use(authMiddleware);
app.use(loggingMiddleware);
app.use(rateLimiter);
Each one wraps and adds functionality before and/or after the main handler.
Logging middleware can log requests/responses.
Rate limiter can restrict based on limits.
✅ 4. UI Frameworks: Component Styling
In GUI libraries (like JavaFX, Swing, React), decorators are used for styling or wrapping components:
java
CopyEdit
JComponent button = new JButton("Click Me");
button = new BorderDecorator(button);
button = new ShadowDecorator(button);
Or in React:
jsx
CopyEdit
const BaseButton = () => <button>Click</button>;
const WithBorder = (Component) => () => <div className="border">{Component()}</div>;
const WithShadow = (Component) => () => <div className="shadow">{Component()}</div>;
const FancyButton = WithShadow(WithBorder(BaseButton));
✅ 5. Security Filters (Spring Security)
Spring Security wraps HTTP requests/responses with security filters:
java
CopyEdit
UsernamePasswordAuthenticationFilter
→ wraps → Request
→ adds authentication logic
Each filter is a decorator that adds specific behavior like:
Authentication
Authorization
CORS headers
CSRF protection
✅ 6. E-commerce Pricing Rules
java
CopyEdit
Price basePrice = new BasePrice(100);
basePrice = new SeasonalDiscountDecorator(basePrice);
basePrice = new LoyaltyDiscountDecorator(basePrice);
Base price is $100.
Seasonal discount removes 10%.
Loyalty discount removes another 5%.
Total price calculated dynamically using decorators.
✅ Summary Table
Domain
Decorator Usage
Java I/O Streams
Buffered, Compressed, Encrypted Streams
Spring Framework
Transactional, Security, Logging Proxies
Web Middleware (Express) Auth, Logging, Error Handlers
UI Components
Styles, Themes, Layouts
E-commerce
Discounts, Coupons, Taxes
0
You can add this document to your study collection(s)
Sign in Available only to authorized usersYou can add this document to your saved list
Sign in Available only to authorized users(For complaints, use another form )