1
\$\begingroup\$

I have found this question online from an Amazon interview:

Design a kind of Kindle Fire application where we can subscribe to a news channel and read the news from all publishers as a digital format.

I thought that using the Observer design pattern would be a good solution here.

Here is my implementation:

UnitTest class

[TestClass]
    public class ObserverPatternTest
    {
        [TestMethod]
        public void TestMethod1()
        {
            NewsChannel pub1 = new NewsChannel();
            NewsChannel pub2 = new NewsChannel();

            KindleApplication app = new KindleApplication();
            pub1.Subscribe(app);
            pub2.Subscribe(app);
            string tempStory = "story start";

            pub1.NewStory= tempStory;
            Assert.AreEqual(tempStory, app.Message);
            tempStory = " story b";
            pub1.NewStory = tempStory;
            Assert.AreEqual(tempStory, app.Message);
            tempStory = "story pub2";
            pub2.NewStory = tempStory;
            Assert.AreEqual(tempStory, app.Message);
        }
    }

KindleApplication class (Observer)

  public class KindleApplication : ObserverBase
    {
        public string Message { get; set; }
        public override void Update(string message)
        {
            Message = message;
        }
    }

   public abstract class ObserverBase
    {
       public abstract void Update(string message);
    }

NewsChannel class

 public class NewsChannel : publisher
    {
        private string _newStory;

        // Gets or sets subject state
        public string NewStory
        {
            get { return _newStory; }
            set { 
                _newStory = value;
                Notify(_newStory);
            }
        }
    }

Publisher class

 public abstract class publisher
    {
        private List<ObserverBase> _observerList;

        public publisher()
        {
            _observerList = new List<ObserverBase>();
        }
        public void Subscribe(ObserverBase app)
        {
            _observerList.Add(app);
        }
        public void Unsubscribe(ObserverBase app)
        {
            _observerList.Remove(app);
        }
        public void Notify(string message)
        {
            foreach (var obesever in _observerList)
            {
                obesever.Update(message);
            }
        }
    }
\$\endgroup\$
1
\$\begingroup\$

I think in general your implementation is fine. You could however use the intefaces that are already provided by .NET:

  • IObservable<T>

    Defines a provider for push-based notification.

  • IObserver<T>

    Provides a mechanism for receiving push-based notifications.

You can read more about them in the article about the Observer Design Pattern


In your your application you could use them to replace the Publisher and the ObserverBase.

The KindleApplication would become a IObserver<T>:

public partial class KindleApplication<T>
{
    public T Message { get; set; }

    public void Subscribe(IObservable<T> news)
    {
        _newsUnsubscribers[news] = news.Subscribe(this);
    }

    public void Unsubscribe(IObservable<T> news)
    {
        if (_newsUnsubscribers.TryGetValue(news, out var newsUnsubscriber))
        {
            newsUnsubscriber.Dispose();
            _newsUnsubscribers.Remove(news);
        }
    }   
}

and it would implement this interface by storing the observables in a dictionary so that you can unsubscribe again:

public partial class KindleApplication<T> : IObserver<T>
{
    private readonly IDictionary<IObservable<T>, IDisposable> _newsUnsubscribers = new Dictionary<IObservable<T>, IDisposable>();

    public void OnNext(T value)
    {
        Message = value;
    }

    public void OnError(Exception error)
    {
        // ...
    }

    public void OnCompleted()
    {
        // ...
    }
}

The NewsChannel is now an IObservable<T> that notifies all observers about the changes:

public partial class NewsChannel<T>
{
    private T _newStory;

    public T NewStory
    {
        get { return _newStory; }
        set
        {
            _newStory = value;
            Notify();
        }
    }

    private void Notify()
    {
        foreach (var observer in _observers)
        {
            observer.OnNext(_newStory);
        }
    }
}

The new implementation could use an ISet<IObserver<T>> so that you don't subscribe the same channel multiple times and consequently get multiple updates for the same change.

It uses a private class that implements the logic for unsubscribing via the IDisposable interface.

public partial class NewsChannel<T> : IObservable<T>
{
    private ISet<IObserver<T>> _observers = new HashSet<IObserver<T>>();    

    public IDisposable Subscribe(IObserver<T> observer)
    {
        _observers.Add(observer);
        return new Unsubscriber(_observers, observer);
    }

    private class Unsubscriber : IDisposable
    {
        private ISet<IObserver<T>> _observers = new HashSet<IObserver<T>>();

        private IObserver<T> _observer;

        public Unsubscriber(ISet<IObserver<T>> observers, IObserver<T> observer)
        {
            _observers = observers;
            _observer = observer;
        }

        public void Dispose()
        {
            _observers.Remove(_observer);
        }
    }
}

If you like you can split each class into multipe partial files to better separate the implementation of the intefaces from the actual data.


As far as the test is concerned it's really hard to follow it. You should split it into multiple tests that possibly verify only a single case. This means you could have such test as:

  • OnNext_NotSubscribed_DoesNotReciveMessage
  • OnNext_SubscribedToOneFeed_RecivesSingleMessage
  • OnNext_SubscribedToTwoFeeds_RecivesMultipleMessages
  • OnNext_SubscribedAndUnsubscribed_DoesNotReciveMessages
  • etc.
| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.