This content originally appeared on Level Up Coding - Medium and was authored by Rajtilak Pal
Welcome back to the 3rd tutorial of this series. As identified in the previous tutorial, we have a logic issue in the stopwatch. To see the time of the stopwatch, we are just printing out the time but in most applications, we do not want the time to be printed out on the console, (in fact, no application has a console nowadays). We want the stopwatch to return the time asynchronously to the client so that the client can do whatever it wants to do with the time. Seems like a nice design problem, right?
Observer Pattern to the rescue.
Previous Tutorial
If you are reading this part first, you should definitely check out Part 2 of this series to catch up.
Let’s go and see what is this Observer Pattern we are talking about.
There are two main identities involved here:
- Observable
- Observer
Observable is the one who needs to be observed by others. When its internal state changes, it should notify all the Observers who are observing this guy. Let’s take an example to make this concrete.
We all know how Newspaper Subscriptions work, right? (Courtesy of Example: Head First Design Patterns)
- A Newspaper publisher goes into business and begins publishing newspapers.
- You subscribe to a particular publisher, and every time there is a new edition it gets delivered to you. As long as you remain a subscriber, you get new newspapers.
- You may unsubscribe when you don’t want papers anymore, and they stop being delivered.
- While the publisher remains in business, people, hotels, airlines, and other businesses constantly subscribe and unsubscribe to the newspaper publication.
In this example, the Newspaper Publication is Observable and the Customers are Observers.
The formal definition of the Observer pattern is:
The Observer Pattern defines a one-to-many dependency between objects so that when one object changes state, all of its dependents are notified and updated automatically.
How to implement this in code? Take a look at the general class diagram:
There are two main interfaces: Observable and Observer. The Observable interface has three methods: registerObserver(), removeObserver() and notifyObservers().
- registerObserver() is responsible for subscribing an observer to this observable.
- removeObserver() is responsible for the unsubscription of an observer.
- notifyObservers() is responsible for notifying all the observers of the change of state by calling each of the observers’ update() method.
The Observer interface has only one method: update(). This method is responsible for receiving the updated state of the observable and dealing with it.
Comparing it with our example, we can make our Stopwatch class implement Observable because the clients want to observe the time of the Stopwatch and all the clients will implement Observer. The class diagram of our application will look like this:
Notice here we have not implemented the removeObserver() method in the Observable interface because it is a very trivial method and you can, yourself, figure out the removal logic.
Let’s start by making the two interfaces:
Observable.java
public interface Observable {
public void registerObserver(Observer o);
public void notifyObservers();
}
In the registerObserver() method of the Observable, an observer is getting passed to the observable and we will store it internally using an ArrayList when we write the concrete class.
Observer.java
public interface Observer {
public void update(long time);
}
In the update() method of the Observer, the updated time is getting passed from the Observable and the observer can, then, do anything with it.
Next, we start by implementing the Observable interface and its methods in our Stopwatch class.
public class Stopwatch implements Runnable, Observable
{
...
@Override
public void registerObserver(Observer o) {
}
@Override
public void notifyObservers() {
}
}
Let’s make an ArrayList to store our observers and initialize it in the constructor(Don’t forget to import it).
import java.util.*;
public class Stopwatch implements Runnable, Observable
{
...
private ArrayList<Observer> observers;
public Stopwatch()
{
...
observers = new ArrayList<>();
}
}
When the registerObserver() is called, we want to add it to the ArrayList.
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
Now, we write the notifyObservers() method. We will just loop through all our Observers and call their update() methods. But we will follow another small design pattern here which you will encounter very often: Iterator Pattern. The advantage of using Iterators are immense but for now, understand that Iterator is just like a Cursor which saves the state of an iteration and has very handy methods: hasNext() and next() which hides the internal implementation of the underlying data structure and helps us loop through the data structure without even worrying about what it’s made up of. Don’t get carried away, it’s pretty simple to understand. Just see the code.
@Override
public void notifyObservers() {
Iterator<Observer> iter = observers.iterator();
while(iter.hasNext())
{
Observer o = iter.next();
o.update(getTime());
}
}
Here, the observers ArrayList has a method, iterator(), that returns us the Iterator object. And then we can loop through the observers list using only the hasNext() and next() methods of the “iter” object. hasNext() returns “true” if there are more observers and next() returns the next observer in the ArrayList. And then we call the update() method of each observer and pass the time using the getTime() method.
One last thing that is left in the Stopwatch class is to update the run() method, which currently prints out the time, to notifying all the observers. Every 10 ms, the thread should notify all the observers. That was our goal right?
@Override
public void run() {
while(!isStopped)
{
notifyObservers();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
For the Observable part, you might be thinking: let’s make StopwatchTest class implement Observable. You are right to some extent but because of the presence of the static main() method, we might have to take a detour. You will understand why this is a problem only when you will implement it yourself and get a compile-time error.
We will make a separate class for the Observer which will only be responsible for printing out the time. We are doing the same print again but, trust me, this design pattern is going to help us so much when we add Swing to it.
We make a new file named StopwatchObserver.java which implements the Observer interface.
public class StopwatchObserver implements Observer{
@Override
public void update(long time) {
System.out.print("\rTime: "+time);
}
}
This is the full code of the stopwatch observer. That’s it.
Now, we modify our StopwatchTest class to test out the stopwatch with this new design. We just need to add two more lines to it: create an observer and register the observer. All the printing will be handled by the observer itself.
public class StopwatchTest {
public static void main(String[] args) {
Stopwatch sw = new Stopwatch();
StopwatchObserver ob = new StopwatchObserver();
sw.registerObserver(ob);
// Rest of the start and stop methods remain same
}
}
That’s it. We are trying hard to keep our promise of keeping the client-side code as small as possible. Here the client only has to implement the Observer interface and boom: the work is done. Now, let’s take a look at the output.
The output stays the same but the way the output is brought about to the screen is changing every time. It’s getting better and better with the use of more powerful design patterns.
Great! Now we have our Stopwatch class completely ready for commercial use. It’s a great time to build an application that uses this Stopwatch. From the next tutorial, we will start with our Swing Application and make a proper stopwatch app. See you in the next one.
Next Tutorial:
Full Code
Observable.java
public interface Observable {
public void registerObserver(Observer o);
public void notifyObservers();
}
Observer.java
public interface Observer {
public void update(long time);
}
Stopwatch.java
import java.util.ArrayList;
import java.util.Iterator;
public class Stopwatch implements Runnable, Observable
{
private long offset, currentStart;
private boolean isStopped;
private Thread th;
private ArrayList<Observer> observers;
public Stopwatch()
{
offset = 0L;
currentStart = System.currentTimeMillis();
isStopped = true;
observers = new ArrayList<>();
}
public void start()
{
if(isStopped)
{
th = new Thread(this);
th.start();
currentStart = System.currentTimeMillis() - offset;
}
isStopped = false;
}
public void stop()
{
if(!isStopped)
{
th = null;
offset = System.currentTimeMillis() - currentStart;
}
isStopped = true;
}
public long getTime()
{
if(!isStopped)
return System.currentTimeMillis() - currentStart;
else
return offset;
}
@Override
public void run() {
while(!isStopped)
{
notifyObservers();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void notifyObservers() {
Iterator<Observer> iter = observers.iterator();
while(iter.hasNext())
{
Observer o = iter.next();
o.update(getTime());
}
}
}
StopwatchObserver.java
public class StopwatchObserver implements Observer{
@Override
public void update(long time) {
System.out.print("\rTime: "+time);
}
}
StopwatchTest.java
public class StopwatchTest {
public static void main(String[] args) {
Stopwatch sw = new Stopwatch();
StopwatchObserver ob = new StopwatchObserver();
sw.registerObserver(ob);
System.out.println("\nStarting Stopwatch: ");
sw.start();
sleep(2000);
sw.stop();
System.out.println("\nStopwatch Stopped. ");
sleep(2000);
System.out.println("\nStarting Stopwatch: ");
sw.start();
sleep(2000);
sw.stop();
System.out.println("\nStopwatch Stopped. ");
}
public static void sleep(int time)
{
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
References:
- Book: Head First Design Patterns, by Eric Freeman, Elisabeth Robson, Bert Bates, Kathy Sierra.
Applying the Observer Pattern (Part 3) was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Level Up Coding - Medium and was authored by Rajtilak Pal
Rajtilak Pal | Sciencx (2021-03-26T12:23:01+00:00) Applying the Observer Pattern (Part 3). Retrieved from https://www.scien.cx/2021/03/26/applying-the-observer-pattern-part-3/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.