Code duplication with composition and observer pattern?

Viewed 319

Suppose I have two classes with downstream subscriptions (subscribers)

class A {
   SubscriptionManager<A> subscriptionManager; // downstream subscriptions
   void addSub(Subscription<A> sub){
      subscriptionManager.addSub(sub);
   }
}

class B {
   SubscriptionManager<B> subscriptionManager; // downstream subscriptions
   void addSub(Subscription<B> sub){
      subscriptionManager.addSub(sub);
   }
}

Here is the issue I am dealing with, and its a design problem. I've heard several times of composition over inheritance, in an example like this I could create an abstract class which holds the subscription manager and has functions for adding and removing subscribers.

However, compoisiton over inheritance is making me not use the abstract class. I'm wondering whats correct here? clearly you see that theres code duplication with the whole add method for either of A, B classes (and perhaps more in the future).

2 Answers

You're right that composition over inheritance is an important principle to keep in mind; composition is often better because it gives you more flexibility.

In this cause though, in my opinion an abstract base class is a justifiable choice. Think about the "is a" question: when one class extends another class, then there is an "is a" relationship going from the subclass to the superclass. Your classes A and B are both things that you can subscribe to, so an A is a Subscribable.

abstract class Subscribable<T> {
    private SubscriptionManager<T> subscriptionManager;

    protected Subscribable(SubscriptionManager<T> subscriptionManager) {
        this.subscriptionManager = subscriptionManager;
    }

    void addSub(Subscription<T> sub) {
        subscriptionManager.addSub(sub);
    }
}

class A extends Subscribable<A> {
    public A(SubscriptionManager<A> subscriptionManager) {
        super(subscriptionManager);
    }
}

class B extends Subscribable<B> {
    public B(SubscriptionManager<B> subscriptionManager) {
        super(subscriptionManager);
    }
}

I'm wondering whats correct here?

These are design choices and there's rarely a single, objectively correct choice - every design choice has its own advantages and drawbacks.

Some OO programming languages support mixins which help you implement something like this with composition instead of inheritance, but Java does not support this very well.

Your example doesn't violate DRY principle. You're simply using an object which promises to handle subscriptions. The thin layer around it is not duplicating any knowledge, it is delegating properly.

Another approach you could take is to make your clients (A and B) be concrete factories which will produce a SubscriptionManager. That way their client would get to interact with the manager directly and you would not have to write wrap methods.

class A {
   SubscriptionManager<A> subscriptionManager; // downstream subscriptions

   /**
    * There is some tension with Law of Demeter here,
    * but these are the decisions you have to make as a designer. 
    */
   SubscriptionManager<A> subManager() {
      //note: SubscriptionManager should be abstract to avoid tight coupling.
      return subscriptionManager;
   }
}
Related