The Observer design pattern
Tags: designpatterns, java, observerdesignpattern, pubsub
Published: 2023-12-20 11:56:53

The Observer design pattern is also called Publish-Subscribe pattern and is the core principle of the Reactive paradigm (PUSH in favor of PULL).

#### Design Considerations

There are many **observers** (subscribers that are interested in some data) that are observing a **publisher** (which maintains that data). Observers register themselves to the publisher. The publisher can then push data to these observers, since it maintains a list of subscribed observers (PUSH!), whenever there is a change in the publisher’s state. 

This pattern defines a one-to-many dependency between objects such that when the state of one object changes, all its dependents are notified and updated automatically.

**Example**: You are working on the user microservice. When a user is created, the following activities have to be performed:

1. Send out a welcome email to the user.
2. Generate and send a verification token to this user using 
which they can verify their account.

One way of structuring our code would be:

    public void createUser(CreateUserCommand command) {
        //omitted - code to create and save a user
        sendWelcomeEmail();
        generateAndSendVerificationToken();
    }

This violates both the **S** and **O** of the **SOLID** principles. Your UserService is doing too much work and is not extensible. Let's assume, as part of the user creation process, we now need to create an Avatar for the user as well. Your code would then become:

    public void createUser(CreateUserCommand command) {
        //omitted - code to create and save a user
        sendWelcomeEmail();
        generateAndSendVerificationToken();
        generateAvatar();
    }

#### Instead, let's try to use the Observer design pattern

The data

    public record UserCreated(String userId, String email, String firstName, String lastName) {}

The Publisher interface

    public interface Publisher<T> {
        void subscribe(Subscriber<T> subscriber);
        void unsubscribe(Subscriber<T> subscriber);
        void publish(T data);
    }

The Subscriber interface

    public interface Subscriber<T> {
        void next(T data);
    }

Let's create the concrete Publisher and Subscribers

    public class UserPublisher implements Publisher<UserCreated> {

        private final List<Subscriber<UserCreated>> subscribers = new ArrayList<>();

        @Override
        public void subscribe(Subscriber<UserCreated> subscriber) {
            subscribers.add(subscriber);
        }

        @Override
        public void unsubscribe(Subscriber<UserCreated> subscriber) {
            subscribers.remove(subscriber);
        }

        @Override
        public void publish(UserCreated data) {
            subscribers.forEach(s -> s.next(data));
        }

    }

-----

    public class UserCreatedNotifier implements Subscriber<UserCreated>{

        @Override
        public void next(UserCreated data) {
            System.out.println("Sending email to " + data.email());
        }

    }

-----

    public class VerificationTokenGenerator implements Subscriber<UserCreated> {

        @Override
        public void next(UserCreated data) {
            System.out.println("Generating verification token and sending to " + data.email());
        }
    }

Let's test it out

    public void createUser(CreateUserCommand command) {
        //... User creation code

        UserPublisher userPublisher = new UserPublisher();

        UserCreatedNotifier userCreatedNotifier = new UserCreatedNotifier();
        VerificationTokenGenerator verificationTokenGenerator = new VerificationTokenGenerator();

        userPublisher.subscribe(userCreatedNotifier);
        userPublisher.subscribe(verificationTokenGenerator);

        userPublisher.publish(new UserCreated("user-1", "user1@gmail.com", "User", "1"));
    }

![Output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/psze6a52wja0j89m1y0m.png)

Now, let's work on the Avatar generation process and see how extensible this design pattern is:

    public class AvatarGenerator implements Subscriber<UserCreated> {
        @Override
        public void next(UserCreated data) {
            System.out.println("Generating avatar for user " + data.email());
        }

    }

-----

    //same code as before    
    AvatarGenerator avatarGenerator = new AvatarGenerator();
    userPublisher.subscribe(avatarGenerator);

![Output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lossdt0ym80fqueeyr7c.png)

#### Advantages

**Loose Coupling**: When two objects are loosely coupled, they can interact but have a very little knowledge of each other. The observer design pattern provides an object design where the publisher and subscribers are loosely coupled:
The only thing a publisher knows about a subscriber is that it implements the subscriber interface. We can add new/remove subscribers at any time since the only thing the publisher depends on is a list of objects that implement the subscriber interface.

Changes to either publisher or subscribers will not affect each other.