The Decorator Design Pattern is a structural pattern that enables you to attach new behaviors or responsibilities to objects without modifying their existing structure. It’s an alternative to subclassing, where you would typically inherit and extend a class to add new functionality. The Decorator Pattern is particularly useful when there are several optional behaviors that you want to add to a service without directly modifying its implementation. it does that  by placing these objects inside special wrapper objects that contain the behaviors.

In this article, we will first explore the Decorator Pattern with a simple example using a UserService that checks for unique usernames. Then, we will discuss the drawbacks of making a small modification in this case and when it’s more appropriate to use the Decorator Pattern, using a Notification example.

Let’s consider a UserService class that is responsible for managing users in an application. One of the requirements is to ensure that a new user has a unique username before saving it to the database.

public interface UserService {
    void save(User user);
    List<User> findAll();
    Optional<User> findByUsername(String username);
}

public class UserServiceImpl implements UserService {
    public void save(User user) {
         // to be implement
    }

    public List<User> findAll() {
         // to be implement
    }

    public Optional<User> findByUsername(String username) {
     // to be implement
     
    }
}

Now, let’s create a decorator to add the unique username check before saving the user:

public abstract class UserServiceDecorator implements UserService {
    protected UserService decoratedUserService;

    public UserServiceDecorator(UserService decoratedUserService) {
        this.decoratedUserService = decoratedUserService;
    }

    public void save(User user) {
        decoratedUserService.save(user);
    }

    public List<User> findAll() {
        return decoratedUserService.findAll();
    }
}


public class UniqueUsernameUserServiceDecorator extends UserServiceDecorator {
    public UniqueUsernameUserServiceDecorator(UserService decoratedUserService) {
        super(decoratedUserService);
    }

    public void save(User user) {
        // Check for a unique username before saving the user
        if (!isUsernameUnique(user)) {
            throw new IllegalArgumentException("Username already exists");
        }
        super.save(user);
    }

    private boolean isUsernameUnique(User user) {
        // Check the database for the existence of the username
        Optional<User> existingUser = decoratedUserService.findByUsername(user.getUsername());
        return existingUser.isEmpty();
    }
}

In this example, the UniqueUsernameUserServiceDecorator class implements the UserService interface and adds the unique username check behavior to the UserService without directly modifying its implementation.

While using the decorator pattern in the UserService example does work and adheres to the decorator principle, it might not be the most efficient or practical solution for such a simple change. The decorator pattern is more appropriate when you need to add multiple optional or configurable behaviors to a class without modifying its existing implementation.

In the UserService example, we only added a single behavior to check if the username is unique. This change could have been directly added to the save method in the UserServiceImpl class, which would be a more straightforward and less complex solution. Using the decorator pattern in this scenario could be considered overkill, as it adds more classes and complexity to the codebase without offering significant benefits in this case.

However, it is important to note that the decorator pattern can be useful in other situations where multiple optional or configurable behaviors need to be added to a class. In such cases, the pattern helps to keep the codebase maintainable and flexible


Example 2: Notification Service Decorator

Now less consider a NotificationService that sends notifications to users. We may want to add various optional behaviors, such as sending bold notifications, urgent notifications, or reminders.

Here is the NotificationService interface and a concrete implementation:

public interface NotificationService {
    void sendNotification(String message);
}

public class BasicNotificationService implements NotificationService {
    public void sendNotification(String message) {
        // Send a basic notification to the user
    }
}

By using the Decorator Pattern, we can create separate decorators for each behavior and combine them as needed without modifying the base NotificationService implementation. This approach allows for more flexibility and maintainability in the long run.

public interface NotificationService {
    void sendNotification(String message);
}
public class BasicNotificationService implements NotificationService {
    @Override
    public void sendNotification(String message) {
        System.out.println("Sending basic notification: " + message);
    }
}


public abstract class NotificationServiceDecorator implements NotificationService {
    protected NotificationService decoratedNotificationService;

    public NotificationServiceDecorator(NotificationService decoratedNotificationService) {
        this.decoratedNotificationService = decoratedNotificationService;
    }

    @Override
    public void sendNotification(String message) {
        decoratedNotificationService.sendNotification(message);
    }
}


public class BoldNotificationDecorator extends NotificationServiceDecorator {
    public BoldNotificationDecorator(NotificationService decoratedNotificationService) {
        super(decoratedNotificationService);
    }

    @Override
    public void sendNotification(String message) {
        String boldMessage = makeBold(message);
        super.sendNotification(boldMessage);
    }

    private String makeBold(String message) {
        return "**" + message + "**";
    }
}


public class UrgentNotificationDecorator extends NotificationServiceDecorator {
    public UrgentNotificationDecorator(NotificationService decoratedNotificationService) {
        super(decoratedNotificationService);
    }

    @Override
    public void sendNotification(String message) {
        String urgentMessage = addUrgency(message);
        super.sendNotification(urgentMessage);
    }

    private String addUrgency(String message) {
        return "[URGENT] " + message;
    }
}


public class ReminderNotificationDecorator extends NotificationServiceDecorator {
    public ReminderNotificationDecorator(NotificationService decoratedNotificationService) {
        super(decoratedNotificationService);
    }

    @Override
    public void sendNotification(String message) {
        String reminderMessage = addReminder(message);
        super.sendNotification(reminderMessage);
    }

    private String addReminder(String message) {
        return "[REMINDER] " + message;
    }
}

Now you can use these decorators to create different combinations of notification services:

public class Main {
    public static void main(String[] args) {
        var basicNotificationService = new BasicNotificationService();

        // Create a bold notification service
        var boldNotificationService = new BoldNotificationDecorator(basicNotificationService);
        boldNotificationService.sendNotification("This is a bold notification");

        // Create an urgent and bold notification service
        var urgentBoldNotificationService = new UrgentNotificationDecorator(boldNotificationService);
        urgentBoldNotificationService.sendNotification("This is an urgent and bold notification");

        // Create a reminder notification service
        var reminderNotificationService = new ReminderNotificationDecorator(basicNotificationService);
        reminderNotificationService.sendNotification("This is a reminder notification");
    }
}