Thursday, March 23, 2017

Let's create a Thermometer using the Observer design pattern - Github repo provided

1. Requirement


Whenever there is a change in temperature, the temperature should be displayed in Celsius and Fahrenheit.

2. Design


  • com.rama41222.main
    • ObserverMain.java - Concrete Class
  • com.rama41222.observer
    • Observable.java - Abstract class
    • Subject.java - Concrete Class
  • com.rama41222.observer.thermometer
    • TemperatureListener.java - Abstract class
    • Thermometer.java - Concrete Class
  • com.rama41222.subscriber
    • CelsiusSubscriber.java - Concrete class
    • FahrenheitSubscriber.java - Concrete Class

3. How it works ?


  1. Thermometer prototype will generate random temperatures.
  2. Whenever there is a temperature change, the change will be sent to Subject through the Thermometer interface.
  3. The subject implements the Thermometer interface. The changed temperature is set to its local Temperature variable through the overridden method from Thermometer interface.
  4. Since the temperature is stored in the Subject, whenever a subscriber subscribe to the Subject through Observable interface, they'll receive the temperature only when the temperature is changed.
  5. Once the temperature is received. it will be converted and displayed.
Observer Design Pattern - The big picture[1]

4. Let's get started

The Thermometer -  Generate random temperatures in 3000ms intervals


package com.rama41222.observer.thermometer;

import java.util.Random;

public class Thermometer implements Runnable {

    private TemperatureListner tm;
    private static final Random RAND = new Random();

//Assignment of the Temperature listener
    public Thermometer(TemperatureListner t) {
        this.tm = t;
    }

//Generate random temperatures between 105 and 90 Fahrenheit in 
//3000 ms intervals
    @Override
    public void run() {

        while (true) {

            try {
                Thread.sleep(3000);
                int no = RAND.nextInt(105 - 90) + 90;
//Passing new TemperatureListner to the interface
                tm.newTemperature(no);
            } catch (InterruptedException ex) {
                System.err.println(ex.getMessage());;
            }
        }
    }

}

Temperature Interface - Communication link between the subject and the thermometer

package com.rama41222.observer.thermometer;

public interface TemperatureListner {
    public void newTemperature(double temp);
}

Subject - Acts as a server class which facilitates the addition of observers, removal of observers, notifying observers etc. This implements the TemperatureListner interface above. 

In this case, the subject is eagerly instantiated(Singleton)

package com.rama41222.observer;

import com.rama41222.observer.thermometer.TemperatureListner;
import com.rama41222.observer.thermometer.Thermometer;
import java.util.ArrayList;
import java.util.List;

public class Subject implements TemperatureListner {

    private final static List<Observer> observers = new ArrayList<>();
    private static final Subject s = new Subject();
    private static final Thermometer tmo = new Thermometer(s);
    double temp;

    private Subject() {}

    public static void startThermo() {
// Creating a new thread and starting the Thermometer  If multithreading isn't use, it will interrupt the main thread which will cause this program to stop.
        Thread task = new Thread() {
            @Override
            public void run() {
                tmo.run();
            }
        };
        task.start();
    }
// Singleton getInstance method
    public static Subject getInstance() {
        return s;
    }
// Adding the subscribers to the observers list
    public void setObserver(Observer s) {
        observers.add(s);
    }

    private double getTemp() {
        return this.temp;
    }
// Looping throw all the obverses in the list
    public void notifyObservers() {
        observers.stream().forEach((ob) -> {
//setting the data for a particular subscriber in the list through the observer interface
            ob.valueChange(this.temp);
        });
    }

    @Override
    public void newTemperature(double temp) {
        this.temp = temp;
// Notify all the subscribers in the static list
        notifyObservers();
    }
}

Observer Abstract Class - The main communication link between the subscribers and the subject. The subject sends the data through this interface to all the subscribers whenever there's a change in the temperature.

package com.rama41222.observer;

public abstract class Observer {

    protected Subject subject;

    public abstract void valueChange(double temp);
}

Subscriber Class - Shown below is the celsius class. You can have a Fahrenheit class ass well.

package com.rama41222.subscriber;

import com.rama41222.observer.Observer;
import com.rama41222.observer.Subject;

public class SubscriberCelsius extends Observer {
// subject is declared in the observer abstract class.
    public SubscriberCelsius(Subject s) {
        this.subject = s;
        this.subject.setObserver(this);
    }
// Implementing the Observer interface. Since the data is already available through the interface, we just have to format and print the temperature.
    @Override
    public void valueChange(double temp) {
        double f = (temp - 32) * 5 / 9;
        double rf = Math.round(f * 100) / 100;
        System.out.println("Temp Celsius " + rf);
    }

}
Now you have fully implemented the observer design pattern to develop a demo thermometer. The only step remaining is to run the program.


ObserverMain - Main method is defined here.( Nothing to do with the observer pattern)

package com.rama41222.main;

import com.rama41222.observer.Subject;
import com.rama41222.subscriber.SubscriberCelsius;
import com.rama41222.subscriber.SubscriberFarenheit;


public class ObserverMain {

    public static void main(String[] args) {
// Get the instance of the subject 
        Subject subject = Subject.getInstance();
// Turn on the thermometer 
        Subject.startThermo();
// Add subscribers 
        new SubscriberCelsius(subject);
        new SubscriberFarenheit(subject);
// Subscribe to getState method in the subject, to get temperature changes.
        subject.getState();
        subject.getState();
    }
}

Now run the main class and check the results

GITHUB Repo

References 


[1] https://www.tutorialspoint.com/design_pattern/observer_pattern.htm





1 comment:

  1. vety usefull article brother .. explained clearly :)

    ReplyDelete