Delgan
Delgan

Reputation: 19677

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

Apologies for this quite abstract title.

More clearly:

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?

Upvotes: 1

Views: 67

Answers (1)

Oliv
Oliv

Reputation: 18081

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>;

Upvotes: 2

Related Questions