Search code examples
c++design-patternspolymorphismabstract-classabstraction

How to handle abstraction and specialization of a class and its attributes?


Apologies for this quite abstract title.

More clearly:

  • I have two classes Controler and Interface (hardware sense, unrelated to design pattern)
  • Both are abstract with some pure virtual methods and hence intended to be subclassed
  • I need each Controler created to be associated with an Interface object
  • Each Controler subclass only work with a subset of Interface subclasses (ControlerA + InterfaceA or ControlerB + InterfaceB but not ControlerA + InterfaceB)
  • Each Interface subclass has its own methods not inherited (this is why only one kind of Controler can use it)
  • The Controler base class need to call some method of the base class Interface

I try to pass an Interface objet to the Controler constructor, hence in my class definition the Interface attribute represents the abstract base class. But if my Controler subclass A need to call a specific method of the Interface A, an compilation error is raised as the Interface base class doesn't own this method.

The only workaround I found was to call dynamic_cast, but it obviously seems wrong.


Here are my Interface classes:

class Interface {
public:
  Interface() {};
  virtual void method() = 0;
};

class InterfaceA : public Interface {
public:
  InterfaceA() : Interface() {};
  void method() override { cout << "A overriding" << endl; }
  void onlyA() { cout << "A only" << endl; }
};

class InterfaceB : public Interface {
public:
  InterfaceB() : Interface() {};
  void method() override { cout << "B overriding" << endl; }
  void onlyB() { cout << "B only" << endl; }
};

Here are my Controler classes:

class Controler {
public:
  Controler(Interface* i) : m_interface(i) {};
  virtual void uniqueMethod() = 0;
  void commonMethod() { m_interface->method(); }
  Interface* m_interface;
};

class ControlerA : public Controler {
public:
  ControlerA(InterfaceA* i) : Controler(i) {};
  void uniqueMethod() override {dynamic_cast<InterfaceA *>(m_interface)->onlyA();}
};

class ControlerB : public Controler {
public:
  ControlerB(InterfaceB* i) : Controler(i) {};
  void uniqueMethod() override {dynamic_cast<InterfaceB *>(m_interface)->onlyB();}
};

And here is how I plan to use them:

auto ia = new InterfaceA();
auto ca = ControlerA(ia);
ca.commonMethod();  // Method defined in the base class
ca.uniqueMethod();  // Method defined in InterfaceA only

You can try it on Repl.it.

Is there any design pattern to solve this issue?


Solution

  • There is a problem indeed. There exists an invariant between the dynamic type of m_interface and the dynamic type of the object that implement Controler. But this invariant cannot be maintained by the Controler class. So the m_interface member is not a the right place.

    The consequence is that you need to check at runtime that this member has the right type by using the dynamic_cast each time you call uniqueMethod. If the invariant is broken, the code will have UB because it would dereference a null pointer.

    So this is not really a design pattern issue, but more fundamentally an object oriented programming recommendation: classes must ensure invariants.

    class Controler {
    public:
      virtual void uniqueMethod() = 0;
      virtual void commonMethod() = 0;
    };
    
    class ControlerA : public Controler {
    public:
      ControlerA(InterfaceA* i):m_interface{i} {
        assert(dynamic_cast<InterfaceA*>(i)!=nullptr);
        };
      void uniqueMethod() override { m_interface->onlyA();}
      void commonMethod() override { m_interface->method(); }
    private: InterfaceA* m_interface;
    };
    
    class ControlerB : public Controler {
    public:
      ControlerB(InterfaceB* i):m_interface{i} {
        assert(dynamic_cast<InterfaceB*>(i)!=nullptr);
        };
      void uniqueMethod() override { m_interface->onlyB();}
      void commonMethod() override { m_interface->method(); }
    private: InterfaceB* m_interface;
    };
    

    So now, it looks that we have a regular pattern, so this is where we can think about a more generic design:

    template<class Inter,void(Inter::* OnlyFunc)()>
    class ControlerImpl : public Controler {
    public:
      ControlerImpl(Inter* i):m_interface{i} {
        assert(dynamic_cast<Inter*>(i)!=nullptr);
        };
      void uniqueMethod() override { (m_interface->*OnlyFunc)();}
      void commonMethod() override { m_interface->method(); }
      private: Inter* m_interface;
    };
    using ControlerA = ControlerImpl<InterfaceA,&InterfaceA::onlyA>;
    using ControlerB = ControlerImpl<InterfaceB,&InterfaceB::onlyB>;