Showing posts with label Observer Design Pattern. Show all posts
Showing posts with label Observer Design Pattern. Show all posts

Saturday, March 14, 2020

Python: Implementing Flexible Logging Mechanism

This post is presenting a way to implement flexible logging mechanism for Python program. However, just for the sake of being curious, I have also implemented another logging mechanism by using Observer Design Pattern. The both programs can be downloaded from my github page.

For flexible logging, I have the following two criteria:
  1. Number of loggers (medium into which log feed will be written) can be chosen.
  2. Log feed (which will be written into chosen medium) can be chosen, based on log levels.

Using Observer Pattern


import datetime as dt   
import enum

class LogLevel(enum.Enum):
    debug = 1
    info = 2
    warning = 3
    error = 4
    critical = 5
    
# subscriber in observer pattern, receives updates from publisher.
class LogHandler:
    def __init__(self, logging_function, log_level):
        self.logging_function = logging_function
        self.log_level = log_level
    
    # receive update from publisher
    def update_log(self, message, log_level):
        # log levels: debug=1, info=2, warning=3, error=4, critical=5
        # ex. class log level 1 will send log updates for all incoming messages
        if(self.log_level.value <= log_level.value):
            date_time_string = str(dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
            self.logging_function('{} {} {}'.format(date_time_string, log_level.name, message))
        
# publisher in observer pattern, send updates for subscribers.
class Logger:
    def __init__(self):
        self.log_handlers = set()
        
    def register_log_handler(self, log_handler):
        self.log_handlers.add(log_handler)
        
    def unregister_log_handler(self, log_handler):
        self.log_handlers.discard(log_handler)
    
    # send update for all registered subscribers
    def send_log_update(self, message, log_level):
        for log_handler in self.log_handlers:
            log_handler.update_log(message, log_level)  

# create publisher
logger = Logger()

# create console log handler (subscriber), receiving updates only when update message log level is greater than/equal to warning
console_log_handler = LogHandler(lambda message: print(message), LogLevel.warning)
logger.register_log_handler(console_log_handler)

# create file log handler (subscriber), receiving all possible updates
log_file = open('/home/mikejuniperhill/log.txt', 'w')
file_log_handler = LogHandler(lambda message: print(message, file=log_file), LogLevel.debug)
logger.register_log_handler(file_log_handler)

# process log updates
logger.send_log_update('sending calculation task to engine', LogLevel.debug)
logger.send_log_update('engine is processing calculation task', LogLevel.info)
logger.send_log_update('incorrect grid configurations, using backup grid settings', LogLevel.warning)
logger.send_log_update('analytical error retrieved for this calculation task', LogLevel.error)
logger.send_log_update('unable to process calculations task due to incorrect market data', LogLevel.critical)

log_file.close()

Log from processing feed is being printed to console and file, according to the chosen log levels.









Using Python Logging


Python is full of positive surprises and as one might expect, logging issues have already been thoroughly chewed by Python community. More information on in-built logging tools can be read from here. In the program, we create loggers and handlers (for console and file) by using method Initialize_logHandlers. This method uses Configurations object, which will be constructed from a given json file. The content of json configuration file is as follows.

{
  "LOGFORMATSTRING": "%(asctime)s %(levelname)-1s %(filename)-1s %(funcName)-1s %(message)s", 
  "CLEANLOGFILEBEFOREWRITE": 1, 
  "LOGFILEPATH": "/home/mikejuniperhill/log.txt",
  "CONSOLELOGLEVEL": 30,
  "FILELOGLEVEL": 10, 
  "LOGDATEFORMAT": "%d.%m.%Y %H:%M:%S",
  "LOGFILEMODE": "w"
}

The program below will create configurations object from json file, initializes logging handlers (console and file) and process small feed for logger.

import os, logging, json

class Configurations:
    inner = {}
    # read JSON configuration file to dictionary
    def __init__(self, filePathName):
        self.inner = json.load(open(filePathName))
    # return value for a given configuration key
    # 'overload' indexing operator
    def __getitem__(self, key):
        return self.inner[key.upper()]

def Initialize_logHandlers(configurations):
    # conditionally, delete the existing log file before starting to write log for this session
    if(bool(int(configurations['CleanLogFileBeforeWrite'])) == True):
        path = configurations['LogFilePath']
        if(os.path.exists(path) == True): os.remove(path)
 
    # create logger
    logger = logging.getLogger()
    logger.setLevel(logging.NOTSET)
 
    # console log handler
    c_handler = logging.StreamHandler()
    c_handler.setLevel(int(configurations['ConsoleLogLevel']))
    c_formatter = logging.Formatter(configurations['LogFormatString'], datefmt=configurations['LogDateFormat'])
    c_handler.setFormatter(c_formatter)
    logger.addHandler(c_handler)
 
    # file log handler
    f_handler = logging.FileHandler(configurations['LogFilePath'], mode=configurations['LogFileMode'], encoding=None, delay=True)
    f_handler.setLevel(int(configurations['FileLogLevel']))
    f_formatter = logging.Formatter(configurations['LogFormatString'], datefmt=configurations['LogDateFormat'])
    f_handler.setFormatter(f_formatter)
    logger.addHandler(f_handler)

# read configurations for this program
path = '/home/mikejuniperhill/configurations.json'
# create configurations object
configurations = Configurations(path)

# initialize log handlers
Initialize_logHandlers(configurations)
 
# process log updates
logging.debug('sending calculation task to engine')
logging.info('engine is processing calculation task')
logging.warning('incorrect grid configurations, using backup grid settings')
logging.error('analytical error retrieved for this calculation task')
logging.critical('unable to process calculations task due to incorrect market data')

Log from processing feed is being printed to console and file, according to the chosen log levels.







Finally, thanks for reading this blog.
-Mike

Sunday, December 13, 2015

Implementing statistics gatherer design by using Boost library in C++

As a result of Monte Carlo simulation process, we get a lot of simulated values. After this, we usually want to calculate a set of desired statistics from those simulated values. A great statistics gatherer design has been thoroughly presented in The book by Mark Joshi. However, just for the curious and for the sake of trying to create something original (while respecting traditions), I wanted to implement my own statistics gatherer design, in which several different statistical measures could be calculated independently by using the same set of processed data. Moreover, the number of such calculation objects and their implementations for algorithmic details would be fully configurable. Finally, I wanted to avoid any direct coupling between data (object, which holds the source data) and algorithms (objects, which are performing the actual statistical calculations).

Why bother?


In the following example program below, a sample of discounted payoffs from Monte Carlo process has been hard-coded and two statistical measures are then calculated and printed out.

void MonteCarloProcess()
{
 // process has been producing the following discounted payoffs
 // for this sample : average = 10.4881, standard error = 1.58502
 double discountedPayoffs[] = { 18.5705, 3.31508, 0.0, 3.64361, 0.0, 0.0, 
  47.2563, 10.6534, 85.5559, 0.0, 30.2363, 0.0, 17.8391, 2.15396, 0.0, 
  66.587, 0.0, 9.19303, 0.0, 0.0, 24.2946, 29.6556, 0.0, 0.0, 0.0, 65.926, 
  0.0, 14.0329, 1.43328, 0.0, 0.0, 0.0, 1.37088, 0.0, 2.49095, 21.4755, 
  36.5432, 0.0, 16.8795, 0.0, 0.0, 0.0, 19.8927, 11.3132, 37.3946, 10.2666, 
  26.1932, 0.0, 0.551356, 29.7159, 0.0, 31.5357, 0.0, 0.0, 4.64357, 4.45376, 
  21.6076, 12.693, 16.0065, 0.0, 0.0, 0.0, 0.0, 25.9665, 18.7169, 2.55222, 
  25.6431, 8.5027, 0.0, 0.0, 29.8704, 0.0, 22.7266, 22.8463, 0.0, 0.0, 0.0, 
  0.0, 4.90832, 13.2787, 0.0, 0.0, 9.77076, 24.5855, 12.6094, 0.0, 0.0, 1.92343, 
  5.66301, 0.0, 0.0, 13.6968, 0.0, 0.0, 35.2159, 0.0, 8.44648, 7.21964, 0.0, 19.2949 };
 //
 // dump array data into vector
 std::vector<double> data(discountedPayoffs, std::end(discountedPayoffs));
 //
 // calculate and print mean and standard error
 double mean = AlgorithmLibrary::Mean(data);
 double standardError = AlgorithmLibrary::StandardError(data);
 std::cout << mean << std::endl;
 std::cout << standardError << std::endl;
}

There is nothing fundamentally wrong in this example, but a great deal of it could be done in a bit different manner. The scheme presented in this example offers a chance to explore some modern tools for implementing flexible and configurable C++ programs.

Chinese walls


Personally, I would prefer to separate data (discounted payoffs) and algorithms (mean, standard error) completely. Ultimately, I would like to have a design, in which a process (monte carlo) is generating results and adding those results into a separate container. Whenever this process is finished, it will send a reference of this container for several entities, which will calculate all required statistics independently.

There are many ways to implement this kind of a scheme, but I have been heavily influenced by delegates stuff I have learned from C#. Needless to say, the equivalent mechanism for C# delegate in C++ is a function pointer. However, instead of raw function pointer I will use Boost.Function and Boost.Bind libraries.

My new design proposal will have the following components :
  • AlgorithmLibrary for calculating different statistical measures. The header file for this contains collection of methods for calculating different statistical measures.
  • ResultContainer class for storing processed results and function pointers (boost function) which are sending processed results for further calculations.
  • StatisticsElement class for storing a value of statistical measure and function pointer (boost function) for an algorithm, which can be used for calculating required statistical measure.

 

Strangers in the night


In the first stage, StatisticsElement object will be created. This object will host a single statistical measure and a pointer to an algorithm (boost function) for calculating this specific measure. By giving required algorithm as a pointer (boost function), the object is not tied with any hard-coded algorithm. In the case there would be a need for another type for algorithm implementation for calculating required statistical measure, the scheme is flexible enough to be adjusted. In the constructor of this class, we are giving this pointer to an algorithm (boost function). Moreover, this object will be indirectly connected with ResultContainer object with a function pointer (boost function). Function pointer (boost function) will be created and binded (boost bind) with the specific method (process) of StatisticsElement object.

In the second stage, ResultContainer object will be created and all previously created function pointers (boost function, boost bind) for processing calculation results will be added into ResultContainer. This object is ultimately being shared with a process. Process will generate its results (double) and these results will be added into container object. When a process is finished, container object method (sendResults) will be called. The call for this method will trigger a loop, which will iterate through a vector of function pointers (boost function) and sending a reference for a result vector to all connected StatisticsElement objects.

Finally, the client program (in this example : main) will request the calculated statistical measures directly from all StatisticsElement objects. It should be stressed, that these two objects described above, do not have any knowledge about each other at any point. ResultContainer is just storing updates from a process and finally sending results "to somewhere" when the processing is over. StatisticsElement objects are processing their own calculation procedures as soon as they will receive results "from somewhere". It should also be noted, that this design actually implements observer pattern, where ResultContainer object is Observable and StatisticalElement objects are Observers.


Proposal


// ResultContainer.h
#pragma once
#include <vector>
#include <boost\function.hpp>
//
// class for storing processed results and function pointers 
// which are sending results for further processing
class ResultContainer
{
private:
 // container for storing processed results
 std::vector<double> results;
 // container for storing function pointers
 std::vector<boost::function<void
  (const std::vector<double>&)>> resultSenders;
public:
 // method for adding one processed result into container
 void addResult(double result);
 // method for adding one function pointer into container
 void addResultSender(boost::function<void
  (const std::vector<double>&)> resultSender);
 // method for sending all processed results
 void sendResults() const;
};
//
//
//
// StatisticsElement.h
#pragma once
#include <vector>
#include <boost\function.hpp>
//
// class for storing a value of statistical measure and 
// function pointer for an algorithm which can be used 
// for calculating required statistical measure
class StatisticsElement
{
private:
 // statistical measure
 double statisticsValue;
 // function pointer to an algorithm which calculates 
 // required statistical measure
 boost::function<double(const std::vector<double>&)> algorithm;
public:
 // parameter constructor
 StatisticsElement(boost::function<double
  (const std::vector<double>&)> algorithm);
 // method for processing data in order to 
 // calculate required statistical measure
 void process(const std::vector<double>& data);
 // method (overloaded operator) for accessing 
 // calculated statistical measure
 double operator()() const;
};
//
//
//
// AlgorithmLibrary.h
#pragma once
#include <vector>
#include <numeric>
//
// algorithms library for calculating statistical measures
namespace AlgorithmLibrary
{
 // calculate arithmetic average
 double Mean(const std::vector<double>& data) 
 { 
  return std::accumulate(data.begin(), data.end(), 0.0) 
   / data.size(); 
 }
 // calculate standard error estimate
 double StandardError(const std::vector<double>& data) 
 { 
  double mean = AlgorithmLibrary::Mean(data);
  double squaredSum = std::inner_product(data.begin(), 
   data.end(), data.begin(), 0.0);
  return std::sqrt(squaredSum / data.size() - mean * mean) 
   / std::sqrt(data.size());
 }
}
//
//
//
// ResultContainer.cpp
#include "ResultContainer.h"
//
void ResultContainer::addResult(double result)
{
 results.push_back(result);
}
void ResultContainer::addResultSender(boost::function<void
 (const std::vector<double>&)> resultSender)
{
 resultSenders.push_back(resultSender);
}
void ResultContainer::sendResults() const
{
 std::vector<boost::function<void
  (const std::vector<double>&)>>::const_iterator it;
 for(it = resultSenders.begin(); it != resultSenders.end(); it++)
 {
  (*it)(results);
 }
}
//
//
//
// StatisticsElement.cpp
#include "StatisticsElement.h"
//
StatisticsElement::StatisticsElement(boost::function<double
 (const std::vector<double>&)> algorithm) 
 : statisticsValue(0.0), algorithm(algorithm) 
{
 //
}
void StatisticsElement::process(const std::vector<double>& data)
{
 if(algorithm != NULL) statisticsValue = algorithm(data);
}
double StatisticsElement::operator()() const
{
 return statisticsValue;
}
//
//
//
// MainProgram.cpp
#include <boost\bind.hpp>
#include <iostream>
#include "AlgorithmLibrary.h"
#include "ResultContainer.h"
#include "StatisticsElement.h"
//
void MonteCarloProcess(ResultContainer& resultContainer)
{
 // process has been producing the following discounted payoffs
 // for this sample : average = 10.4881, standard error = 1.58502
 double discountedPayoffs[] = { 18.5705, 3.31508, 0.0, 3.64361, 0.0, 0.0, 
  47.2563, 10.6534, 85.5559, 0.0, 30.2363, 0.0, 17.8391, 2.15396, 0.0, 
  66.587, 0.0, 9.19303, 0.0, 0.0, 24.2946, 29.6556, 0.0, 0.0, 0.0, 65.926, 
  0.0, 14.0329, 1.43328, 0.0, 0.0, 0.0, 1.37088, 0.0, 2.49095, 21.4755, 
  36.5432, 0.0, 16.8795, 0.0, 0.0, 0.0, 19.8927, 11.3132, 37.3946, 10.2666, 
  26.1932, 0.0, 0.551356, 29.7159, 0.0, 31.5357, 0.0, 0.0, 4.64357, 4.45376, 
  21.6076, 12.693, 16.0065, 0.0, 0.0, 0.0, 0.0, 25.9665, 18.7169, 2.55222, 
  25.6431, 8.5027, 0.0, 0.0, 29.8704, 0.0, 22.7266, 22.8463, 0.0, 0.0, 0.0, 
  0.0, 4.90832, 13.2787, 0.0, 0.0, 9.77076, 24.5855, 12.6094, 0.0, 0.0, 1.92343, 
  5.66301, 0.0, 0.0, 13.6968, 0.0, 0.0, 35.2159, 0.0, 8.44648, 7.21964, 0.0, 19.2949 };
 //
 // dump array data into vector
 std::vector<double> data(discountedPayoffs, std::end(discountedPayoffs));
 // create vector iterator, loop through data and add items into result container object
 std::vector<double>::const_iterator it;
 for(it = data.begin(); it != data.end(); it++)
 {
  resultContainer.addResult(*it);
 }
 // trigger result processing for all 'connected' statistical element objects
 resultContainer.sendResults();
}
//
int main()
{
 // create : function pointer to mean algorithm, statistics element 
 // and function pointer to process method of this statistics element
 boost::function<double(const std::vector<double>&)> meanAlgorithm = 
  AlgorithmLibrary::Mean;
 StatisticsElement mean(meanAlgorithm);
 boost::function<void(const std::vector<double>&)> resultSenderForMean = 
  boost::bind(&StatisticsElement::process, &mean, _1);
 //
 // create : function pointer to standard error algorithm, statistics element and 
 // function pointer to process method of this statistics element
 boost::function<double(const std::vector<double>&)> standardErrorAlgorithm = 
  AlgorithmLibrary::StandardError;
 StatisticsElement standardError(standardErrorAlgorithm);
 boost::function<void(const std::vector<double>&)> resultSenderForStandardError = 
  boost::bind(&StatisticsElement::process, &standardError, _1);
 //
 // create : result container and add previously created function 
 // pointers (senders) into container
 ResultContainer resultContainer;
 resultContainer.addResultSender(resultSenderForMean);
 resultContainer.addResultSender(resultSenderForStandardError);
 //
 // run (hard-coded) monte carlo process
 MonteCarloProcess(resultContainer);
 //
 // print results from the both statistics elements
 std::cout << mean() << std::endl;
 std::cout << standardError() << std::endl;
 return 0;
}


Help


Concerning the actual installation and configuration of Boost libraries with compiler, there is a great tutorial by eefelix available in youtube. For using Boost libraries, there is a document available, written by Dimitri Reiswich. Personally, I would like to present my appreciations for these persons for their great contribution.

Thanks for reading my blog. Have a pleasant wait for the Christmas.
-Mike

Friday, May 31, 2013

Implementing Observer Design Pattern in VBA with Events

I have "known" VBA Events for years. Once I created one application for hosting some data updating process. To automatize that process, I set a task for opening Excel workbook with Windows Scheduler. As soon as Scheduler was opening Excel workbook in the morning, Workbook_Open() method was kicking in and the magic was happening.

To be honest, for a long time I was lazy to dig deeper and also did not find any other "real" use for VBA Events, except these common Events for Excel workbook objects. I think this is not anything uncommon. If you use Google to find some practical examples, you will find a lot of examples, but they are almost always some examples with VBA User Forms. Anyway, at some point I was doing some semi-serious woodshedding with Observer Design Pattern and started to think about Events again. I started to study these a bit deeper and found out one pretty nice thing: I could create my own Events and use those in my classes. This current post is a result of that discovery.

In this post, I am opening up one possible Observer Design Pattern implementation with VBA Event mechanism. Remember to check my previous post on doing the same thing without VBA Events http://mikejuniperhill.blogspot.fi/2013/05/implementing-observer-design-pattern-in.html

First we create data source class (VBA Class Module, name = DataSource):

Option Explicit
'
Public Event dataFeedUpdate(ByVal marketData As Double)
'
Public Function getMarketDataUpdate(ByVal marketData As Double)
    '
    RaiseEvent dataFeedUpdate(marketData)
End Function
'

Then we create class for all "observers" (VBA Class Module, name = DataObserver):

Option Explicit
'
Private WithEvents source As DataSource
'
Public Function registerPublisher(ByRef dataFeed As DataSource)
    '
    Set source = dataFeed
    Debug.Print "Observer " & ObjPtr(Me) & " registered data feed"
End Function
'
Public Function unRegisterPublisher()
    '
    Set source = Nothing
    Debug.Print "Observer " & ObjPtr(Me) & " unregistered data feed"
End Function
'
Private Sub source_dataFeedUpdate(ByVal marketData As Double)
    '
    Debug.Print "Market data update from observer " & ObjPtr(Me) & ": " & marketData
End Sub
'

Then again, we create one tester program (VBA Standard Module). In tester program, I have been creating one test run, in which observers are registering/unregistering data source, which is sending data feed updates for observers:


Option Explicit
'
Public Sub tester()
    '
    ' create data source object and two observers
    Dim source As New DataSource
    Dim observer1 As New DataObserver
    Dim observer2 As New DataObserver
    '
    ' register datafeed publisher for both observers
    observer1.registerPublisher source
    observer2.registerPublisher source
    '
    ' send data feed
    source.getMarketDataUpdate 100.25
    source.getMarketDataUpdate 100.15
    '
    ' observer2 unregistering, send data feed
    observer2.unRegisterPublisher
    source.getMarketDataUpdate 100.1
    '
    ' observer1 unregistering, send data feed
    observer1.unRegisterPublisher
    source.getMarketDataUpdate 100.1
    '
    ' observer1 re-registering, send data feed
    observer1.registerPublisher source
    source.getMarketDataUpdate 100.2
    '
    ' observer2 re-registering, send data feed
    observer2.registerPublisher source
    source.getMarketDataUpdate 100.25
    '
    ' destroy datafeed and both observers
    Set observer2 = Nothing
    Set observer1 = Nothing
    Set source = Nothing
End Sub
'


Resulting Immediate Window printout (observer1 in blue, observer2 in red):
Observer 237412240 registered data feed
Observer 237411920 registered data feed
Market data update from observer 237412240: 100,25
Market data update from observer 237411920: 100,25
Market data update from observer 237412240: 100,15
Market data update from observer 237411920: 100,15
Observer 237411920 unregistered data feed

Market data update from observer 237412240: 100,1
Observer 237412240 unregistered data feed
Observer 237412240 registered data feed
Market data update from observer 237412240: 100,2

Observer 237411920 registered data feed
Market data update from observer 237412240: 100,25
Market data update from observer 237411920: 100,25


One more thing: when you create your custom event, the object (DataObserver) in which the Event-related function is going to be called, must have reference to its caller (DataSource). Otherwise calling object (DataSource) do not know, what object it should call. I am doing that "binding" in registerPublisher function. This issue was actually causing a lot of grey hairs for me, until I got the point.

This was another possible implementation of Observer DP in VBA by using Event mechanism. Nice to know also. Have a nice weekend!
-Mike



Implementing Observer Design Pattern in VBA without Events

Observer Design Pattern (DP) can be useful tool in your DP toolpack. Now, for what reason is this pattern used for? For example, looking closely to Bloomberg BBCOM API methods and its mechanism, leads me to assume strongly that the market data retrieving process has been implemented by using Observer DP. You could use this pattern, if you get some feed from somewhere and you would like to save the result of that feed to somewhere else, for example.

Anyway, In the following example below, I am presenting one possible VBA implementation of Observer DP, without using Events. First we create "source" class (VBA Class Module, name = DataSource):

Option Explicit
'
Private observers As Collection
Private data As Double
'
Public Function registerObserver(ByRef observer As DataObserver)
    '
    Debug.Print "Observer " & ObjPtr(observer) & " registered"
    observers.Add observer
End Function
'
Public Function unRegisterObserver(ByRef observer As DataObserver)
    '
    Dim i As Integer
    For i = 1 To observers.Count
        If ((ObjPtr(observers(i)) = (ObjPtr(observer)))) Then
            Debug.Print "Observer " & ObjPtr(observers(i)) & " unregistered"
            observers.Remove i
            Exit For
        End If
    Next i
End Function
'
Private Function notifyObservers()
    '
    If (observers.Count > 0) Then
        Dim i As Integer
        For i = 1 To observers.Count
            observers(i).updateMarketData (data)
        Next i
    End If
End Function
'
Public Function getMarketDataUpdate(ByVal marketData As Double)
    '
    data = marketData
    notifyObservers
End Function
'
Private Sub Class_Initialize()
    Set observers = New Collection
End Sub
'
Private Sub Class_Terminate()
    Set observers = Nothing
End Sub
'

Then we create class for "observers" (VBA Class Module, name = DataObserver):

Option Explicit
'
Private data As Double
'
Private Function displayMarketData()
    Debug.Print "Market data update from observer " & ObjPtr(Me) & ": " & data
End Function
'
Public Function updateMarketData(ByVal marketData As Double)
    data = marketData
    displayMarketData
End Function
'

Then we create tester program (VBA Standard Module). In tester program, I have been creating one test run, in which the data source is registering/unregistering observers and sending data feed for them:

Option Explicit
'
Public Sub tester()
    '
    ' create data source object and two observers
    Dim source As New DataSource
    Dim observer1 As New DataObserver
    Dim observer2 As New DataObserver
    '
    ' register both observers
    source.registerObserver observer1
    source.registerObserver observer2
    '
    ' data source sends feed to both observers
    source.getMarketDataUpdate 100.25
    source.getMarketDataUpdate 100.15
    '
    ' unregister observer2, data source sends feed to observer1
    source.unRegisterObserver observer2
    source.getMarketDataUpdate 100.1
    '
    ' unregister observer1, no data feed to any observer
    source.unRegisterObserver observer1
    source.getMarketDataUpdate 100.1
    '
    ' re-register observer1, data source sends feed to observer1
    source.registerObserver observer1
    source.getMarketDataUpdate 100.2
    '
    ' re-register observer2, data source sends feed to both observers
    source.registerObserver observer2
    source.getMarketDataUpdate 100.25
    '
    ' destroy source and observers
    Set observer2 = Nothing
    Set observer1 = Nothing
    Set source = Nothing
End Sub
'

Resulting Immediate Window printout (observer1 in blue, observer2 in red):

Observer 242968104 registered
Observer 242967944 registered
Market data update from observer 242968104: 100,25
Market data update from observer 242967944: 100,25
Market data update from observer 242968104: 100,15
Market data update from observer 242967944: 100,15
Observer 242967944 unregistered

Market data update from observer 242968104: 100,1
Observer 242968104 unregistered
Observer 242968104 registered
Market data update from observer 242968104: 100,2

Observer 242967944 registered
Market data update from observer 242968104: 100,25
Market data update from observer 242967944: 100,25

This was one possible implementation of Observer DP in a nutshell. Nice to know. Have a nice day. 
-Mike