Decorator is a structural design pattern, so now you have a different implementation for each type If you don’t remember, here are them:
Decorator also is a good interview answer for SO(pen-closed)LID question.
Adding new behaviors without change the original object using a Design Pattern(DP). I’m sure the interviewer will like it!
“Why are you saying to not learn?” Because it’s Friday! =D
I’m here to teach you modern Java, and maybe, you don’t need implement a Decorator in your use case. Again, this is not a small tip, but I hope you enjoy it!
Imagine you wrote an user input validation for this class:
public record User (String username, String email, String password) {}
Username: non-empty and at least 5 chars.
Email: valid format need “@” and “.”
Let’s use the Decorator Pattern. To make the chain, I will need to create an extra AlwaysValid
class ( ok, it’s not a big problem) and here is my code:
I have my interface and my abstract Decorator:
interface UserInputValidator {
boolean validate(User user);
}
abstract class UserValidatorDecorator implements UserInputValidator {
protected UserInputValidator decoratedValidator;
public UserValidatorDecorator(UserInputValidator validator) {
this.decoratedValidator = validator;
}
}
The concrete implementations:
class AlwaysValid implements UserInputValidator {
@Override
public boolean validate(User user) {
return true;
}
}
class UsernameValidator extends UserValidatorDecorator {
public UsernameValidator(UserInputValidator validator) {
super(validator);
}
@Override
public boolean validate(User user) {
if (user.username() == null || user.username().length() < 5) {
System.out.println("Invalid username: must be at least 5 characters long.");
return false;
}
return decoratedValidator.validate(user);
}
}
class EmailValidator extends UserValidatorDecorator {
public EmailValidator(UserInputValidator validator) {
super(validator);
}
@Override
public boolean validate(User user) {
if (user.email() == null || !user.email().contains("@") || !user.email().contains(".")) {
System.out.println("Invalid email format.");
return false;
}
return decoratedValidator.validate(user);
}
}
Let’s now call it:
public static void main(String[] args) {
User user = new User("Bruno", "bruno@javatips", "pass");
// Create a validation chain using the decorator pattern
UserInputValidator validator = new EmailValidator(new UsernameValidator(new AlwaysValid()));
// Validate user input
boolean isValid = validator.validate(user);
if (isValid) {
System.out.println("User input is valid.");
} else {
System.out.println("User input is invalid.");
}
}
Output:
Invalid email format.
User input is invalid.
If we need to add now, a password validator, we just need to create a new concrete decorator, and add in the chain UserInputValidator validator = new PasswordValidator(new EmailValidator(new UsernameValidator(new AlwaysValid())));
How can we do better?
If you read my Strategy article, you know I’m thinking about lambdas.
Can we convert each validator in a Function? There are lot of solutions here. I will try to return the message if it’s invalid like this:
public interface UserValidators {
// Static email validator
static String emailValidator(User user) {
if (user.email() == null || !user.email().contains("@") || !user.email().contains(".")) {
return "Invalid email format.";
}
return null;
}
// Static username validator
static String usernameValidator(User user) {
if (user.username() == null || user.username().length() < 5) {
return "Invalid username: must be at least 5 characters long.";
}
return null;
}
// Method to get the list of validation rules
static List<Function<User,String>> getValidationRules() {
List<Function<User,String>> validationRules = new ArrayList<>();
// Add the existing static validators as predicates
validationRules.add(UserValidators::emailValidator);
validationRules.add(UserValidators::usernameValidator);
return validationRules;
}
}
Let’s use this:
User user = new User("Brun", "bruno@javatips", "pass");
UserValidators.getValidationRules().stream()
.map(rule -> rule.apply(user)).filter(Objects::nonNull).reduce((a, b) -> a + "\n" + b)
.ifPresentOrElse(System.out::println, () -> System.out.println("User input is valid."));
Output:
Invalid email format.
Invalid username: must be at least 5 characters long.
Now, add a new validator, it’s just add a new static Function in UserValidators and add to the List. That’s it. Your decorator now it’s rule.apply(user)
Do you want another example? See how Venkat uses in his article Refactoring to Functional Style in Java 8: Applying the Decorator Pattern
Are you ready to try this new approach? Let me know!
Happy coding!
Hi, Bruno! Great tip. How could we classify a design pattern like this, where we include validation functions instead of adding new behaviors? Wouldn't that be a form of Decorator implementation? Best regards!