65.9K
CodeProject is changing. Read more.
Home

Common Observer Pattern implementation using Generics

starIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

1.00/5 (1 vote)

Sep 28, 2011

CPOL
viewsIcon

10289

Customized implementation of the Observer pattern using Generics.

I read this link content and customized the implementation of the Observer pattern using Generics: http://www.dofactory.com/Patterns/PatternObserver.aspx#_self2[^].

My implementation contains two Interfaces (IObserver and ISubject) and two concrete classes (Observer and Subject):

IObserver :
public interface IObserver<T> where T : class 
{
    /// <summary>
    /// update observer according to update action that set to the observer
    /// </summary>
    void Update();

    /// <summary>
    /// element that observer should update it
    /// </summary>
    T Element { get; set; }

}
ISubject :
public interface ISubject
{
    /// <summary>
    /// list of obsrvers
    /// </summary>
    IList<Dashboard.Framework.Observer.IObserver<object>> Observers { get; set; }

    /// <summary>
    /// common update action for observers which it's update action has set to null
    /// </summary>
    Action<ISubject, object> UpdateAction { get; set; }

    /// <summary>
    /// the state of subject
    /// </summary>
    object SubjectState { get; set; }

    /// <summary>
    /// attach a observer to this subject
    /// </summary>
    /// <param name="observer"></param>
    void Attach(Dashboard.Framework.Observer.IObserver<object> observer);

    /// <summary>
    /// detach a observer from this subject
    /// </summary>
    /// <param name="observer"></param>
    void Detach(Dashboard.Framework.Observer.IObserver<object> observer);

    /// <summary>
    /// create new observer with given element and common
    /// update action and attach it to this subject
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="element"></param>
    /// <param name="updateAction"></param>
    void AttachElement<T>(T element, Action<ISubject, 
         object> updateAction = null) where T : class;

    /// <summary>
    /// detach observer that contains given element
    /// </summary>
    /// <typeparam name="T"></typeparam>
    void DetachElement<T>(T element) where T : class;

    /// <summary>
    /// notify observers that they should update
    /// </summary>
    void Notify();
}
Observer :
public class Observer<T> : IObserver<T> where T : class
{
    public T Element { get; set; }
    private ISubject Subject { get; set; }
    private Action<ISubject, T> Action { get; set; }

    public Observer(ISubject subject, T element, 
           Action<ISubject, T> action = null)
    {
        Subject = subject;
        Element = element;
        Action = action ?? subject.UpdateAction;
    }

    public void Update()
    {
        if(Action != null)
            Action.Invoke(Subject, Element);
    }
}
Subject :
public class Subject : ISubject
{
    private IList<IObserver<dynamic>> _observers;

    public IList<IObserver<dynamic>> Observers
    {
        get
        {
            return _observers ?? (_observers = 
                   new List<IObserver<object>>());
        }
        set
        {
            _observers = value;
        }
    }

    public Action<ISubject, object> UpdateAction { get; set; }
    public object SubjectState { get; set; }

    public Subject(Action<ISubject, dynamic> action = null)
    {
        UpdateAction = action;
    }

    public void Attach(IObserver<dynamic> observer)
    {
        Observers.Add(observer);
    }

    public void Detach(IObserver<dynamic> observer)
    {
        Observers.Remove(observer);
    }

    public void AttachElement<T>(T element, Action<ISubject, 
           dynamic> updateAction = null) where T : class
    {
        if (element == null) 
            return;

        Observers.Add(new Observer<dynamic>(this, element, 
                      updateAction ?? UpdateAction));
    }

    public void DetachElement<T>(T element) where T : class 
    {
        var isExist = Observers.Any(p => p.Element == element);

        if (isExist)
            Observers.Remove(Observers.First(p => p.Element == element));
    }

    public void Notify()
    {
        foreach (var vo in Observers)
        {
            vo.Update();
        }
    }
}
For example we have a ComboBox (cb) that containts two items(A, B) and three Textboxes (tb_A, tb_B_1, tb_B_2). when text_A is selected, tb_A is visible and when text_B is selected, tb_B_1 and tb_B_2 are visible. Before starting implementation, its better that we define an enum for SubjectState:
enum VisibilitySubjectStates
{
    A = 1,
    B = 2
}
Then we can use Observer implementation in two way: ONE -> We should define our Subject:
private ISubject _visibilitySubject = new Subject();
then we should attach our Observers:
_visibilitySubject.Attach(
    new Observer<object>(_visibilitySubject, tb_A,
            (subject, frameworkElement) =>
                {
                    frameworkElement.Visibility = VisibilitySubjectStates.A.Equals(subject.SubjectState);
                }
    ));

_visibilitySubject.Attach(
    new Observer<object>(_visibilitySubject, tb_B_1,
            (subject, frameworkElement) =>
                {
                    frameworkElement.Visibility = VisibilitySubjectStates.B.Equals(subject.SubjectState);
                }
    ));

_visibilitySubject.Attach(
    new Observer<object>(_visibilitySubject, tb_B_2,
            (subject, frameworkElement) =>
                {
                    frameworkElement.Visibility = VisibilitySubjectStates.B.Equals(subject.SubjectState);
                }
    ));
Finally we should notify our observers when cb selection changed:
cb_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    var comboBox = (ComboBox) sender;

    //We assume that Value of each Item in cb is type of VisibilitySubjectState
    _visibilitySubject.SubjectState = comboBox.SelectedValue;
    _visibilitySubject.Notify();
}
TWO -> We should define our Subject without initializing:
private ISubject _visibilitySubject;
Now we set the Tag property of Obesrvers with valid SubjectSatate:
tb_A.Tag = VisibilitySubjectState.A;
tb_B_1.Tag = VisibilitySubjectState.B;
tb_B_2.Tag = VisibilitySubjectState.B;
We should initialize my Subject in constructor in this way (with common action for all observer that their UpdateAction is null):
_visibilitySubject =
    new Subject(
        (subject, frameworkElement) =>
            {
                frameworkElement.Visibility = (frameworkElement.Tag ?? (VisibilitySubjectState)0).Equals(subject.SubjectState) ? Visibility.Visible : Visibility.Hidden;
            }
    );
then we should attach our Observers without UpdateAction:
_visibilitySubject.Attach(new Observer<object>(_visibilitySubject, tb_A);

_visibilitySubject.Attach(new Observer<object>(_visibilitySubject, tb_B_1);

_visibilitySubject.Attach(new Observer<object>(_visibilitySubject, tb_B_2);
Finally we should notify our observers when cb selection changed:
cb_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    var comboBox = (ComboBox) sender;

    //We assume that Value of each Item in cb is type of VisibilitySubjectState
    _visibilitySubject.SubjectState = comboBox.SelectedValue;
    _visibilitySubject.Notify();
}