Karthik
Karthik

Reputation: 477

Observer Pattern with Generics

I am trying to learn the Observer pattern after reading the MSDN docs: https://learn.microsoft.com/en-us/dotnet/standard/events/observer-design-pattern. When using with inheritance, it errors and does not allow me to create common observers. Below is my code:

interface Book { }

class ComicBook : Book { }
class ComicBookObserver : IObserver<ComicBook>
{
    public void OnCompleted() { throw new NotImplementedException(); }
    public void OnError(Exception error) { throw new NotImplementedException(); }
    public void OnNext(ComicBook value) { throw new NotImplementedException(); }
}

class Publisher : IObservable<Book>
{
    private List<IObserver<ComicBook>> ComicBookObservers = new List<IObserver<ComicBook>>();
    public IDisposable Subscribe(IObserver<Book> observer) 
    {
        if (observer is IObserver<ComicBook>)
            ComicBookObservers.Add(observer);
        return null;
    }
}

class Main
{
    public static void main(string[] args)
    {
        Publisher p = new Publisher();
        p.Subscribe(new ComicBookObserver());
    }
}

I receive the error on p.Subscribe(new ComicBookObserver());:

Cannot convert from 'ComicBookObserver' to 'System.IObserver<Book>'

Why is the case since A ComicBook is also a Book. Is this not allowed? Would I have to create Subcribe methods for all different types of books?

Upvotes: 2

Views: 1073

Answers (2)

Oliver
Oliver

Reputation: 9002

In your example, ComicBookObserver implements IObserver<ComicBook>, therefore it wants to see ComicBook.

Publisher implements IObservable<Book>, therefore it outputs Book.

The Book interface does not satisfy the more derived ComicBook type required by the ComicBookObserver.

If you need to observe specific types then yes you will need to subscribe multiple times with subscribers specific to those types.

You could use the Rx OfType<ComicBook>() method to create a new observable from your IObservable<Book>.

E.g:

Publisher p = new Publisher();
p.OfType<ComicBook>().Subscribe(new ComicBookObserver());

This has the advantage that only items of type ComicBook will be observed and you won't need to perform any additional type checks.

Upvotes: 4

Glenn Ferrie
Glenn Ferrie

Reputation: 10390

I think you can continue with you pattern, but add one wrinkle. I would make the ComicBook observer implement IObserver<Book> and then in your implementation you need to process ComicBook items and discard the rest. arguably you'll be subscribing other Observers for other types of books. they would follow this pattern as well.

interface Book { }

class ComicBook : Book { }
class OtherBook : Book { }
class ComicBookObserver : IObserver<Book>
{
    public void OnCompleted() { throw new NotImplementedException(); }
    public void OnError(Exception error) { throw new NotImplementedException(); }
    public void OnNext(Book value) 
    {   
        if (value is ComicBook)
        {
            // logic for Comic Book values
        }
    }
}

class OthereBookObserver : IObserver<Book>
{
    public void OnCompleted() { throw new NotImplementedException(); }
    public void OnError(Exception error) { throw new NotImplementedException(); }
    public void OnNext(Book value) 
    {   
        if (value is OtherBook)
        {
            // logic for other book values
        }
    }
}

class Publisher : IObservable<Book>
{
    public IDisposable Subscribe(IObserver<Book> observer) { throw new NotImplementedException(); }
}

class Main
{
    public static void main(string[] args)
    {
        Publisher p = new Publisher();
        p.Subscribe(new ComicBookObserver());
        p.Subscribe(new OtherBookObserver());
    }
}

Upvotes: 2

Related Questions