We use cookies and other tracking technologies to improve your browsing experience on our website, to show you personalized content and targeted ads, to analyze our website traffic, and to understand where our visitors are coming from.
⚠️
GDPR & Cookie Policy Notice
In accordance with data protection regulations; the use of mandatory cookies is required for the core functions of our website to operate, ensure data security, and perform analytics. If you reject the use of cookies, it is not possible to benefit from the services on our website due to technical limitations and data synchronization interruptions. You must consent to the use of cookies to access the content on our site.
Behavioral Patterns: Encapsulating Business Logic with Command and Strategy Patterns
Sustainability and flexibility in software architecture are directly related to how business logic is organized. Decoupling decision mechanisms from execution mechanisms in complex systems prevents code from evolving into a “spaghetti” structure. In this context, the Command and Strategy patterns, defined by the Gang of Four (GoF), are the most powerful tools among behavioral patterns for encapsulating business logic.
Figure 1: Behavioral Patterns: Encapsulating Business Logic with Command and Strategy Patterns.
1. Architectural Role of Behavioral Patterns
In object-oriented programming (OOP), communication between objects and the distribution of responsibilities are of critical importance. Behavioral patterns standardize how objects interact with each other and how responsibilities are shared. Encapsulating business logic means making an algorithm or a request independent of the structure that calls it.
2. Command Pattern: Objectification of Requests
The Command pattern turns a request into a standalone object. This transformation enables the storage of parameters, queuing of operations, logging, and undo/redo functionality.
2.1. Components
Command (Interface): Defines the execute() method that will trigger the operation.
ConcreteCommand: Establishes the link between the Receiver object and the action.
Receiver: The object containing the actual business logic.
Invoker: The triggering structure that knows when to execute the command.
2.2. Technical Implementation (C# Example)
// Receiver: The class that performs the actual operationpublicclassTextEditor {
publicvoid InsertText(string text) => Console.WriteLine($"Text inserted: {text}");
publicvoid DeleteText() => Console.WriteLine("Last character deleted.");
}
// Command InterfacepublicinterfaceICommand {
void Execute();
void Undo();
}
// ConcreteCommandpublicclassInsertCommand : ICommand {
privatereadonly TextEditor _editor;
privatereadonlystring _text;
public InsertCommand(TextEditor editor, string text) {
_editor = editor;
_text = text;
}
publicvoid Execute() => _editor.InsertText(_text);
publicvoid Undo() => _editor.DeleteText();
}
// Invoker: Can maintain command historypublicclassCommandManager {
privatereadonly Stack<ICommand> _history = new Stack<ICommand>();
publicvoid Invoke(ICommand command) {
command.Execute();
_history.Push(command);
}
publicvoid Undo() {
if (_history.Count > 0) _history.Pop().Undo();
}
}
Note: The Command pattern is used in a wide range of applications, from GUI buttons to transaction management. It serves as a foundation for tracking commands, especially in “Saga Pattern” implementations within microservice architectures.
3. Strategy Pattern: Dynamic Change of Algorithms
The Strategy pattern defines a family of algorithms that perform a specific task, encapsulates each one, and makes them interchangeable. This pattern allows the client to choose which strategy to use at runtime.
3.1. When to Use?
If a class contains numerous if-else or switch-case blocks for algorithm selection.
If there are different variations of the same task (e.g., different payment methods, different compression formats).
Although both patterns use the principle of encapsulation, their usage purposes differ structurally:
Feature
Command Pattern
Strategy Pattern
Main Goal
To turn a request/action into an object.
To change how an algorithm is performed.
Focus
“What” will be done.
“How” it will be done.
Timing
Operations can be queued or delayed.
Usually, the most suitable method for the current operation is chosen.
Relationship
Provides decoupling between Invoker and Receiver.
Establishes a polymorphic link between Context and Algorithm.
5. Advanced Techniques and Library Integrations
5.1. MediatR and Command Pattern (.NET)
In modern .NET applications, the Command pattern is usually implemented with the MediatR library. This structure provides In-Process Messaging, allowing Controller classes to be completely cleared of business logic (CQRS - Command Query Responsibility Segregation).
With the advent of lambda expressions, functional interfaces are used instead of creating separate classes for simple strategies. This reduces boilerplate code.
6. Advantages of Encapsulating Business Logic
Open/Closed Principle (OCP): There is no need to modify existing code when adding a new command or strategy to the system. Simply adding a new class is sufficient.
Single Responsibility Principle (SRP): Each class does only its own job. The Invoker handles triggering, the Command handles routing, and the Receiver handles execution.
Testability: Since business logic is divided into small pieces, writing Unit Tests becomes easier. Dependencies can be easily simulated with mock objects.
7. Implementation Notes and Best Practices
State Management: Command objects should store the state information required to undo the operation internally. However, the size of this history should be limited to prevent memory leaks.
Generic Interfaces: Designing interfaces generically in the Strategy pattern allows algorithms working with different data types to be derived from a single template.
Dependency Injection: Both patterns integrate perfectly with DI (Dependency Injection) containers. They can be combined with Factory patterns for strategy changes at runtime.
Conclusion
Command and Strategy patterns are the most effective solutions against the problems of rigidity and fragility encountered during the evolution of software. While Command enables actions to circulate freely within the system by turning them into data packets, Strategy prevents the code from being buried under piles of if-else statements by keeping algorithmic variability under control. The correct combination of these two patterns opens the door to a high-quality, sustainable architecture that minimizes technical debt.