on
the Decorator design pattern: a real world java example
The decorator design pattern allows complementing functionalities of an existing interface without altering the original behavior. It is a common pattern, and it can be used, for example, to add logging, caching, notifications to an exisiting interface while preseving modularity and loose coupling. Let’s assume we have the following interface:
1
2
3
4
5
6
7
8
public interface UsersService {
public final static List<User> userDB = Arrays.asList(new User(1, "user1"), new User(2, "user2"));
User readUserById(int id);
void registerUser(User user);
}
with a User
class:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class User {
int id;
String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return this.id;
}
public String getName() {
return this.name;
}
}
and the following implementation:
1
2
3
4
5
6
7
8
9
10
11
12
public class UsersServiceBasic implements UsersService {
public User readUserById(int id) {
Optional<User> searchedUser = UsersService.userDB.stream().filter(user -> user.getId() == id).findFirst();
return searchedUser.isPresent() ? searchedUser.get() : null ;
}
public void registerUser(User user) {
UsersService.userDB.add(user);
}
}
We want to add logging and caching to UsersServiceBasic
without modifying the initial behavior.
Adding Logging:
1
2
3
4
5
6
7
8
9
public abstract class AbstractLoggingService {
//do some logging initialization here
// for the sake of the example we are just using print
public void logInfo(String message) {
System.out.println("INFO " + message);
}
}
We can now implement the UsersService
and add a logging functionality.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class UsersServiceWithLogging extends AbstractLoggingService implements UsersService {
UsersService decoratedUsersService;
public UsersServiceWithLogging(UsersService decoratedUsersService) {
this.decoratedUsersService = decoratedUsersService;
}
public User readUserById(int id) {
logInfo("looking for user with id " + id);
User user = decoratedUsersService.readUserById(id);
String message = user != null ? "Found user" : "Not found, returning null";
logInfo(message);
return user;
}
public void registerUser(User user) {
decoratedUsersService.registerUser(user);
logInfo("registred new user");
}
}
Let’s do a simple test :
1
2
3
4
5
6
7
UsersService basicService = new UsersServiceBasic();
UsersService usersServiceWithLogging = new UsersServiceWithLogging(basicService);
usersServiceWithLogging.readUserById(10);
Result :
INFO looking for user with id 10
INFO Not found, returning null
1
2
usersServiceWithLogging.readUserById(1);
Result :
INFO looking for user with id 1
INFO Found user
Adding caching:
1
2
3
4
5
6
7
8
9
10
11
12
13
public abstract class AbstractCachingService {
Map<Integer, User> cache = new HashMap<>();
public void cache(User user) {
cache.put(user.getId(), user);
}
public User retrieveFromCache(int id) {
return cache.get(id);
}
}
We can now implement the UsersService
and add a caching functionality.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class UsersServiceWithCaching extends AbstractCachingService implements UsersService {
UsersService decoratedUsersService;
public UsersServiceWithCaching(UsersService decoratedUsersService) {
this.decoratedUsersService = decoratedUsersService;
}
public User readUserById(int id) {
User user = retrieveFromCache(id);
if (user != null ) {
return user;
}
return decoratedUsersService.readUserById(id);
}
public void registerUser(User user) {
decoratedUsersService.registerUser(user);
cache(user);
}
}
Combining several decorators:
It is also possible to combine several decorators or, in other words, decorating a decorator. In this example, we can, for example, add logging functionality to the caching service. The caching service can be passed to the logging service or vice versa.
1
2
3
4
5
6
7
8
9
UsersService basicService = new UsersServiceBasic();
UsersService usersServiceWithLogging = new UsersServiceWithLogging(basicService);
UsersService usersServiceWithCaching = new UsersServiceWithCaching(basicService);
UsersService usersServiceWithCachingAndLogging = new UsersServiceWithCaching(usersServiceWithLogging);