Search code examples
c#wpfmvvmobserver-pattern

Observer pattern implementation with complete layer separation and testability, am I doing it right?


I have doubts regarding my implementation of observer pattern, but with complete separation of concerns.

Example below is not a real life code, but just an example of idea how I want to do it.

In my solution I have two project layers:

  • Desktop layer (views, view models, models)
  • Service library layer (with observers)

My view model is a subject subscribing the observers.

Code in VM:

interface ISubject
{
    void Subscribe(IObserverService observer);
    void Unsubscribe(IObserverService observer);
    void Notify();
}
public class MainWindowViewModel : ViewModelBase, ISubject
{
    private readonly IObserverService _observer1;
    private readonly IObserverService _observer2;
    private ArrayList _observers;

    public MainWindowViewModel(
    IObserver1 observer1,
    IObserver2 observer2)
    {
        _observer1 = observer1;
        _observer2 = observer2;

        ObserverCommand = new DelegateCommand(OnObserverCommand);

        InitProgram();
    }

    private void InitProgram()
    {
        _observers = new ArrayList();

        _observers.Add(_observer1);
        _observers.Add(_observer2);
    }

    public List<IObserverService> Observers { get; set; }

    private void OnSwitchCommand(object obj)
    {
        if (Jeden == true)
        {
            UiModel = _controlsService.SwitchOff();
        }
        else
        {
            UiModel = _controlsService.SwitchOn();
        }
    }

    private void OnObserverCommand(object obj)
    {
        SomeValue++;
    }

    public void Subscribe(IObserverService observer)
    {
        Observers.Add(observer);
    }

    public void Unsubscribe(IObserverService observer)
    {
        Observers.Remove(observer);
    }

    public void Notify()
    {
        Observers.ForEach(x => x.Update(SomeValue));
    }

    public ICommand ObserverCommand { get; private set; }

    private int _someValue;
    public int SomeValue
    {
        get => _someValue;
        set
        {
            _someValue = value;
            InformObservers();
        }
    }

    private void InformObservers()
    {
        foreach (IObserverService x in _observers)
        {
            x.Update(SomeValue);
        }
    }
}

And my observer in service layer is very simple. After Update call from the subject is displaying new MessageBox:

public interface IObserverService
{
    void Update(int someValue);
}
public class Observer1 : IObserver1, IObserverService
{
    public string ObserverName { get; private set; }
    public Observer1(string name)
    {
        this.ObserverName = name;
    }
    public void Update(int someValue)
    {
        MessageBox.Show("New value: " + someValue.ToString() + " for " + ObserverName);
    }
}

Observer2 is same as above.

Right now I have doubts how my constructor supposed to look like, if I want to create a new observer with a name parameter, for example: new Observer1("name1") in this case, keeping separation, should my subject's ctor look like:

public MainWindowViewModel()
{
     _observerService = observerService;
     IObserverService observer1 = new ObserverService("name1");
     IObserverService observer2 = new ObserverService("name2");

     SwitchCommnad = new DelegateCommand(OnSwitchCommand);
     ObserverCommand = new DelegateCommand(OnObserverCommand);

     InitProgram();
}

Is it correct approach? Is it going to be testable? Or I have to inject IObserverService somehow?


Solution

  • it makes sense, that MainWindowViewModel will receive some external observers via constructor:

    public MainWindowViewModel(IObserver1 observer1, IObserver2 observer2)
    {
        _observer1 = observer1;
        _observer2 = observer2;
    
        ObserverCommand = new DelegateCommand(OnObserverCommand);
    
        InitProgram();
    }
    

    when you create an instance of MainWindowViewModel (I assume, it will be used for MainWindowView DataContext), you will pass some real observers:

     IObserverService observer1 = new ObserverService("name1");
     IObserverService observer2 = new ObserverService("name2");
     var vm = new MainWindowViewModel(observer1, observer2);
     mainWindow.DataContext = vm;
    

    no need for DI container here if dependencies can be resolved statically

    similarly, for test you can have some TestObserverService (or IObserverService mock):

     IObserverService observer1 = new TestObserverService("name1");
     IObserverService observer2 = new TestObserverService("name2");
     var vm = new MainWindowViewModel(observer1, observer2);
    

    MainWindowViewModel might create some IObserverServices, if it has properties worth observing from other objects in the application (e.g. related view models)