Skip to content

0037: ProgressManager using Observer (and Composite) pattern

07/06/2011

-Hi, everybody!
-Hi dr. Nick 🙂

Some time ago I changed my work team and I moved under Andrzej’s wings:) It was a good change, because since I started work with C# I’ve learned quite much about technical aspects of programming, but not so much about designing of the applications. Of course, during my career I designed and implemented
few applications, but they were rather small – nothing as spectacular as, for example, OLGAtherer 🙂 Joke of course. Anyway, this change let me start to learn from, in my opinion,
very skillful analytic/designer that all the time teaches me new approach to the coding (I removed rest of the sentences that glorified Andrzej).

So, today I’d like to tell about ProgressManager which I found in the application that I work with (I’m a support developer that helps Andrzej to maintain and develop quite large application).
ProgressManager has a form of class with static methods. This class is visible for all parts of the application. When some process start, this has to be done:

  1. Start Process
  2. Create new instance of ProgressManager Window (form)
  3. Set it up
  4. Proceed with computations in background worker
  5. During processing, usually in “foreach” loops, invoke ProgressManger static method that updates previously shown window with progress data.
  6. If Cancel button has been pressed, process won’t stop until it goes back to the specific point (technical restrictions that weren’t overcome, but left as they were)

Above tells us, that this solution sucks. So Andrzej proposed that we should change this:

  1. First stage consists of about one day of checking application, it’s coupling, connections, and so on
  2. After this excessive searching we were able to determine how the process works now (I know, this situation may look unbelieveable, but this happens when there is no planning at the beginning and strange persons develop code…)
  3. In the third stage I had to clean and reorganize the code (background workers mostly), that it would be able to get major changes
  4. Fourth stage will be described in details below. In short, here we had to design new Progress Manager. We did it successfully.
  5. Then I had to implement it in exemplary project and test it in dummy process.
  6. When we were ready to proceed to the next stage, our manager told us to stop. LOL – this is probably the main problem in our work – crappy manager :)))) All the work for nothing:)

OK, this beginning was definitely too long. Now I will tell you about our new ProgressManager.

New ProgressManager is based on Observer pattern. In short, this pattern consists of Observer(s) that observes objects 🙂 Objects know that there is observer (or observers) and when something important happens, they notifies observers about it. To implement it, we had to create new interface: IObserver:

///<summary> 
/// Implemented by class that will observe IObservable object(s).
/// </summary>
public interface IObserver
{
   ///<summary>
   /// Invoked by IObservable, notifies the observer about change of its state.
   /// </summary>
   void Update(IObservable sender, StageState state);
}

This interface contains only one method. This method accepts StageStage object that contains progress data and instance of sender. The instance isn’t necessary in all cases, but we will use it in the Manager.
Now IObservable:

///<summary> 
/// Implemented by class which state will be observable by one or more observers.
/// </summary>
public interface IObservable
{
   ///<summary>
   /// Adds Observer.
   /// </summary>
   void RegisterClient(IObserver client);

   ///<summary>
   /// Removes Observer.
   /// </summary>
   void UnregisterClient(IObserver client);

   ///<summary>
   /// Notifies all observers.
   /// </summary>
   void UpdateClients();

   ///<summary> 
   /// Stops work.
   /// </summary>
   void Stop();

   ///<summary>
   /// Informs about the object's speed.
   /// </summary>
   ClassSpeed Speed { get; }
}

As you can see, every observable object must implement the IObservable. This interface contains two method to add/remove observers, one method to notify clients (observers) and one property that I
very like – ClassSpeed enumeration looks like this:

///<summary>
/// Defines speed that object works with. Maximum value equals 100.
/// </summary>
public enum ClassSpeed
{
   VeryFast = 20,
   Fast = 40,
   Normal = 60,
   Slow = 80,
   VerySlow = 100,
}

It describes how fast is the process relatively. This will allow us to make progress bar changing not with the same speed – I will describe this later.

OK, now the ProgressManager class:

///<summary> 
/// Represents stage of the process or set of the stages, and notifies Observer(s) when the state is being changed.
/// </summary>
public class ProgressManager : IObserver, IObservable
{
#region Data

///<summary> 
/// Observed subprocesses. Generic list of IObservable.
/// </summary>
private List m_ObservedStages = new List();

///<summary> 
/// Registered Observers. Generic list of IObserver.
/// </summary>
private List m_RegisteredListeners = new List();

///<summary> 
/// List of doubles that describes estimated distibution of the times necessary to do the whole job.
/// First item on this list matches with first item on the m_ObservedStages list and indicates how long this stage could be.
/// </summary>
private List m_PercentageDistribution = new List();

///<summary> 
/// Last stage sent to the clients.
/// </summary>
private StageState m_StageState;

///<summary> 
/// Name of the process described by this Manager.
/// </summary>
private string m_ProcessName = string.Empty;

///<summary> 
/// Currently running stage.
/// </summary>
private IObservable m_CurrentObservable;

///
<summary> /// Estimated speed of this Process.
/// </summary>

public ClassSpeed Speed
{
   get{
       double speed = 0;
       m_ObservedStages.ForEach(x =&gt; speed += (int)x.Speed);
       speed /= (m_ObservedStages.Count * 20);
       speed = Math.Round(speed, 0, MidpointRounding.AwayFromZero | MidpointRounding.ToEven);
       return (ClassSpeed)(speed * 20);
       }
}

#endregion Data

#region Ctor

///<summary> 
/// Standard Ctor.
/// </summary>
public ProgressManager(string processName, List stagesToObserve)
{
   m_ProcessName = processName;
   m_ObservedStages = stagesToObserve;
   Subscribe();
   ComputePercentageDistribution();
}

///<summary> 
/// If this Ctor is used, user should ensure that ConfigureManager method will be invoked before Process starts.
/// </summary>
public ProgressManager()
{
}

#endregion Ctor

#region Methods

#region IObservable Members

///<summary> 
/// Adds Observer.
/// </summary>
public void RegisterClient(IObserver client)
{
   m_RegisteredListeners.Add(client);
}

///<summary> 
/// Removes Observer.
/// </summary>
public void UnregisterClient(IObserver client)
{
   m_RegisteredListeners.Remove(client);
}

///<summary> 
/// Notifies all observers.
/// </summary>
public void UpdateClients()
{
   foreach (IObserver client in m_RegisteredListeners)
      client.Update(this, m_StageState);
}

///<summary> 
/// Stops work.
/// </summary>
public void Stop()
{
   m_CurrentObservable.Stop();
}

#endregion IObservable Members

#region IObserver Members

///<summary> 
/// Invoked by IObservable, notifies the observer about change of its state.
/// </summary>
public void Update(IObservable sender, StageState state)
{
   m_CurrentObservable = sender;
   m_StageState = ProcessState(sender, state);
   UpdateClients();
}

#endregion IObserver Members

///<summary> 
/// Configures Manager before Process starts.
/// </summary>
public void ConfigureManager(string processName, List stagesToObserve)
{
   m_ProcessName = processName;
   m_ObservedStages = stagesToObserve;
   Subscribe();
   ComputePercentageDistribution();
}

///<summary> 
/// Registers this Observer to the stages.
/// </summary>
private void Subscribe()
{
   foreach (IObservable stage in m_ObservedStages)
      stage.RegisterClient(this);
}

///<summary> 
/// Computes time that stages will take.
/// </summary>
private void ComputePercentageDistribution()
{
   int totalSpeed = 0;
   m_ObservedStages.ForEach(x =&gt; totalSpeed += (int)x.Speed);
   m_ObservedStages.ForEach(x =&gt; m_PercentageDistribution.Add((int)x.Speed * (double)100 / totalSpeed));
}

///<summary> 
/// Processes state received from Observable and modifies it to the form that will be send to Observers.
/// </summary>
private StageState ProcessState(IObservable sender, StageState state)
{
   int percendageDone = ComputePercentageDone(sender, state);
   StageState newState = new StageState(m_ProcessName, new List()
   {
      new ProgressBarStatus(state.ProcessName, percendageDone, m_ObservedStages.IndexOf(sender), m_ObservedStages.Count),
      state.MainProgressBar,
      state.SecondaryProgressBar
    });
   return newState;
}

///<summary> 
/// Computes overall percentage of the process.
/// </summary>
private int ComputePercentageDone(IObservable sender, StageState state)
{
   int senderIndex = m_ObservedStages.IndexOf(sender);
   double percentage = 0;
   for (int i = 0; i &lt; senderIndex; i++) { percentage += m_PercentageDistribution[i]; } if(state.MainProgressBar.PercentageDone != 0) percentage += (m_PercentageDistribution[senderIndex] *      state.MainProgressBar.PercentageDone / 100); return percentage &gt; 100 ? 100 : (int)percentage;
}

#endregion Methods

HUH. Large piece of code.
First of all you can see that ProgressManager implements IObserver AND IObservable. First we wanted to make him only an Observer, but then we thought that we could imagine situation like this:

(When I created the picture above Andrzej realized me that ProgressManager is rather wrong name. This class should be rather called ProcessManager. Why? Please rotate image above for 90 degrees right. Do you see it? This structure doesn’t only utilizes Observer pattern. There is also Composite that I described here – please notice that drawing here is very similar to the color graphic attached to that post.)

What is also nice here, that the manager is quite “smart”. Depending on observed objects’ speed, it calculates percentage that one subprocess should take. So if we have, for example two subprocesses that are very fast (ClassSpeed = 20) and one subprocess very slow (ClassSpeed = 100), then three subprocesses will take 140 units, and first one will take 20/140 of the overall time, second one the same, and last one will take 100/140 (about 70 percent) of the overall process time. So this would be silly to split progress bar for three parts of the same length – instead of that, ProgressManager will compute percentage distribution and will take care of proper updating of progress bar.

OK, so we have process, subprocesses and ProgressManager(s). How ProgressManager reports to the user? This should be obvious 🙂 We create new form:

public partial class ProgressManagerView : Form, IObserver

Smart, isn’t it? 🙂 Our endpoint is Observer too. It subscribes to the progress manager (only one at the time!), and its main method is:

///<summary>
/// Receives state from observed object and fills controls with its properties.
/// </summary>
public void Update(IObservable sender, StageState state)
{
   SetProgressBarValue(progBarMain, state.MainProgressBar.PercentageDone);
   SetProgressBarValue(progBarSecond, state.SecondaryProgressBar.PercentageDone);
   SetProgressBarValue(progBarThird, state.ThirdProgressBar.PercentageDone);

   SetLabelText(lblCurrentFileName, state.ThirdProgressBar.Title);
   SetLabelText(lblCurrentStageName, state.SecondaryProgressBar.Title);
   SetLabelText(lblProcessName, state.ProcessName);
}

Reading method above you can see that form consists of three progress bars and three labels that are updated not straightforward but via helper methods. Exemplary code of one of these methods:

///<summary> 
/// Helper method preventing from CrossThreadExceptions.
/// Sets label's text.
/// </summary>
private void SetLabelText(Label label, string text)
{
   if (label.InvokeRequired)
   {
      LabelDelegate = new SetTextDelegate(SetLabelText);
      this.Invoke(LabelDelegate, label, text);
   }
   else
      label.Text = text;
}

As you can see, we have to check whether desired control requires to be invoked. If so, we attach this method to delegate and call Invoke method on the form. This technique prevents from exception CrossThreadExceptions when one thread tries to access control created in the other thread. This is described in details here.
Picture below shows endpoint form:

As you can see, there are three changeable labels and three progress bars. Top bar/label describes currently processed file (package), middle bar/label is responsible for displaying
progress of the subprocess, and lower bar/label shows total progress of the whole process. Of course, we can show/hide these components as we want.

This should be all. ProgressManager (ProcessManager?) is almost finished. You’re free to use it (in case of getting this code over, please notice receiver that it was downloaded from this site) – you can download solution here. In case of any questions, feel free to ask.
Best regards
Paweł

Advertisements
Leave a Comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: