- Decorator
- Adapter
- Facade
This project extends an existing support ticket management system with three classic structural design patterns. These patterns increase flexibility, improve extensibility, and decouple components without modifying the existing business logic.
The system initially contained:
- Customer and ticket models
- Repositories (in-memory)
- Logging
- Notification channels (Email, SMS, Push)
- CustomerService and TicketService
- A simple CLI interface
To fulfill the laboratory requirements, three structural patterns were added:
- Decorator Pattern — Enhancing the logging mechanism
- Adapter Pattern — Integrating an external chat notification API
- Facade Pattern — Simplifying common workflows for clients
The following sections describe each pattern, their purpose, implementation details, and usage inside the project.
The Decorator pattern allows behavior to be added to an object dynamically, without modifying its original class. This is used when we want to wrap an existing object with additional responsibilities.
Why here?
The system already had a ConsoleLogger implementing ILogger. We wanted:
- to add timestamps to log messages,
- without modifying ConsoleLogger,
- and without changing any existing service that uses ILogger.
The Decorator perfectly fits this use case.
class ILogger {
public:
virtual ~ILogger() = default;
virtual void log(const std::string& message) = 0;
};class ConsoleLogger : public ILogger {
public:
void log(const std::string& msg) override;
};class LoggerDecorator : public ILogger {
protected:
std::shared_ptr<ILogger> inner;
public:
explicit LoggerDecorator(std::shared_ptr<ILogger> logger);
void log(const std::string& msg) override;
};class TimestampLogger : public LoggerDecorator {
public:
TimestampLogger(std::shared_ptr<ILogger> logger);
void log(const std::string& message) override;
};auto baseLogger = std::make_shared<ConsoleLogger>();
auto logger = std::make_shared<TimestampLogger>(baseLogger);All domain services now automatically use timestamped logging without any code changes to services or loggers.
- Logging improved without modifying existing components
- Multiple decorators could be easily chained (file logger, color logger, etc.)
- Services remain unaware of logging enhancements
- Open for extension, closed for modification (SOLID: OCP)
The Adapter pattern converts the interface of a class into another interface clients expect.
This is used when:
- We need to integrate a third-party component
- The third-party API does not follow our internal interface
- Changing existing interfaces is undesirable
Why here?
The system has its own INotificationChannel interface.
We added a simulated 3rd-party chat API:
class ExternalChatAPI {
public:
void postToChannel(const std::string& channelId, const std::string& text);
};This API is incompatible with the internal notification system.
class INotificationChannel {
public:
virtual bool send(const std::string& recipient,
const std::string& message) = 0;
virtual std::string getChannelName() const = 0;
};class ExternalChatAPI {
public:
void postToChannel(const std::string& channelId,
const std::string& text);
};class ChatNotificationAdapter : public INotificationChannel {
private:
std::shared_ptr<ExternalChatAPI> api;
public:
ChatNotificationAdapter();
bool send(const std::string& recipient, const std::string& msg) override;
std::string getChannelName() const override;
};The adapter is added into the notification system:
notifier.addChannel(std::make_shared<ChatNotificationAdapter>());Now the system treats the external API just like any other notification channel (Email, SMS, Push), without any code changes to existing components.
- Clean integration of an external API
- NotificationService needs no modifications
- Follows the “program to interface” principle
- New alternative channels can be easily added (Slack, Discord, WhatsApp…)
The Facade pattern provides a simplified API over a set of complex subsystems.
In our system, registering a customer and opening a support ticket requires interacting with:
CustomerServiceTicketServiceNotificationService
A CLI or UI should not need to know this complexity.
class SupportFacade {
private:
std::shared_ptr<CustomerService> customerService;
std::shared_ptr<TicketService> ticketService;
NotificationService& notifier;
public:
std::pair<std::string, std::string> registerCustomerAndOpenTicket(
const std::string& name,
const std::string& email,
const std::string& phone,
const std::string& issueDescription,
Priority priority,
TicketCategory category
);
};- Register customer
- Create ticket
- Notify customer
The CLI only calls:
facade.registerCustomerAndOpenTicket(...);instead of interacting with three different services.
Menu option:
3. Register customer + create ticket (Facade)
Triggers:
auto [customerId, ticketId] = facade.registerCustomerAndOpenTicket(...);- Simplifies CLI logic
- Encapsulates multi-step workflows in one place
- Supports future UI layers (web, desktop, REST API)
- Reduces coupling between client code and business logic
| Pattern | Purpose | Implementation Location | System Benefit |
|---|---|---|---|
| Decorator | Add responsibilities dynamically | TimestampLogger |
Improved logging without modifying services |
| Adapter | Convert external API to internal interface | ChatNotificationAdapter |
Seamless integration of external chat notifications |
| Facade | Simplify subsystem usage with unified interface | SupportFacade |
Cleaner, simpler CLI & easier workflows |
By implementing three structural design patterns, the system became:
- more modular
- easier to extend
- easier to integrate with external systems
- decoupled and aligned with SOLID principles
These patterns demonstrate clear, practical improvements in real-world software architecture and provide a strong foundation for future enhancements such as GUI/REST APIs, additional notification channels, or advanced logging systems.