Предоставление защищенного API извне в дополнение к внутреннему API с иерархией классов в C ++ - PullRequest
0 голосов
/ 02 марта 2019

Я работаю над библиотекой C ++, для которой существует семейство абстрактных классов, которым некоторым разработчикам, использующим эту библиотеку, в зависимости от их потребностей, потребуется создать подкласс для взаимодействия с остальной частью библиотеки.Это было сочтено необходимым, потому что сама библиотека должна иметь возможность создавать экземпляры и использовать эти классы внутри (своего рода система плагинов).Эти классы имеют protected API для взаимодействия с остальной частью библиотеки.Они также нуждаются в отдельном внутреннем интерфейсе для управления экземплярами, предоставляя указатели на внутренние структуры и вызывая другие функции управления, которые должны быть прозрачными для конечных производных классов.

Текущее решение включает создание внутреннего базового класса для управления.интерфейс для каждого открытого класса, а функциональность внутренней библиотеки просто приводит к этому классу, когда ему необходимо выполнить функции управления.

Вот упрощенный пример:

class BaseInternal
{
public:
  void doSomething();
};

class BaseComponent
{
public:
  BaseComponent();
  virtual ~BaseComponent();
  void manageComponent();
  void setInternal(std::shared_ptr<BaseInternal>);
protected:
  const std::shared_ptr<const BaseInternal> getInternal() const;
  const std::shared_ptr<BaseInternal> getModInternal();
private:
  std::shared_ptr<BaseInternal> internal;
};

class Base : public BaseComponent
{
public:
  Base();
  virtual ~Base();
protected:
  // library "plugin" API
  virtual void overrideMe() = 0;
  virtual void overrideMe2() = 0;
  void foo() { getModInternal()->doSomething(); }
private:
  // Component access hidden here
  using BaseComponent::manageComponent;
  using BaseComponent::setInternal;
  using BaseComponent::getInternal;
  using BaseComponent::getModInternal;
};

class AInternal
{
public:
  void doASomething();
};

class AComponent
{
public:
  AComponent();
  virtual ~AComponent();
  void manageA();
  void setAInternal(std::shared_ptr<AInternal>);
protected:
  const std::shared_ptr<const AInternal> getAInternal() const;
  const std::shared_ptr<AInternal> getModAInternal();
private:
  std::shared_ptr<AInternal> aInternal;
};

class A : public AComponent, virtual public Base
{
public:
  A();
  virtual ~A();
protected:
  // library "plugin" API for A
  void bar() { getModAInternal()->doASomething(); BaseComponent::getModInternal()->doSomething();}
private:
  // Component access hidden here
  using AComponent::manageA;
  using AComponent::setAInternal;
  using AComponent::getAInternal;
  using AComponent::getModAInternal;
};

class BInternal
{
public:
  void doBSomething();
};

class BComponent
{
public:
  BComponent();
  virtual ~BComponent();
  void manageB();
  void setBInternal(std::shared_ptr<BInternal>);
protected:
  const std::shared_ptr<const BInternal> getBInternal() const;
  const std::shared_ptr<BInternal> getModBInternal();
private:
  std::shared_ptr<BInternal> bInternal;
};

// Base
class B : public BComponent, virtual public Base
{
public:
  B();
  virtual ~B();
protected:
  // library "plugin" API for B
  void baz() { getModBInternal()->doBSomething(); BaseComponent::getModInternal()->doSomething();}
private:
  // Component access hidden here
  using BComponent::manageB;
  using BComponent::setBInternal;
  using BComponent::getBInternal;
  using BComponent::getModBInternal;
};

class CInternal
{
public:
  void doCSomething();
};

class CComponent
{
public:
  CComponent();
  virtual ~CComponent();
  void manageC();
  void setCInternal(std::shared_ptr<CInternal>);
protected:
  const std::shared_ptr<const CInternal> getCInternal() const;
  const std::shared_ptr<CInternal> getModCInternal();
private:
  std::shared_ptr<CInternal> bInternal;
};

// Derived virtually from A and B since there may be future classes
// derived from C
class C : public CComponent, virtual public A, virtual public B
{
public:
  C();
  virtual ~C();
protected:
  // library "plugin" API for C
  void qux() {
    getModCInternal()->doCSomething();
    AComponent::getModAInternal()->doASomething();
    BComponent::getModBInternal()->doBSomething();
    BaseComponent::getModInternal()->doSomething();
  }
private:
  // Component access hidden here
  using CComponent::manageC;
  using CComponent::setCInternal;
  using CComponent::getCInternal;
  using CComponent::getModCInternal;
};

Разработчики, использующие эту возможность, могутзатем определите их классы так:

class MyBasePlugin : public Base
{
  // providing the implementation to the pure virtual Base functions
  void overrideMe() override {foo();}
  void overrideMe2() override {foo();}
};

class MyCPlugin : public C
{
  // providing the implementation to the pure virtual Base functions
  void overrideMe() override {qux();}
  void overrideMe2() override {bar(); baz();}
};

Теперь это позволяет внутреннему коду static_cast или dynamic_cast определить, как управлять экземпляром:

void manageAnInstance(std::shared_ptr<Base> instance)
{
  // If I need to access a base management function, I can static cast
  // since I know all of them are BaseComponent objects
  BaseComponent* base = static_cast<BaseComponent*>(instance.get());
  base->manageComponent();
  base->setInternal(std::make_shared<BaseInternal>());
}

void manageDerivedStuff(std::shared_ptr<Base> instance)
{
  // If I need to access a derived management function, I need to dynamic
  // cast because I don't know for sure that the instance is one of those
  // classes
  C* c = dynamic_cast<C*>(instance.get());
  if(c)
  {
    CComponent* ccomp = static_cast<CComponent*>(c);
    ccomp->manageC();
    ccomp->setCInternal(std::make_shared<CInternal>());
  }

  A* a = dynamic_cast<A*>(instance.get());
  if(a)
  {
    AComponent* acomp = static_cast<AComponent*>(a);
    acomp->manageA();
    acomp->setAInternal(std::make_shared<AInternal>());
  }
}

Есть паравещей, которые мне действительно не нравятся в этом:

  • dynamic_cast s, чтобы определить, как управлять экземпляром
  • . Методология ограничения доступа к функциям управления довольно жалкая.Производный класс может легко обойти эти ограничения доступа, вызвав, например, BaseComponent::manageComponent().
  • наследование компонента, также просто странно.Кажется странным публично наследовать базовый класс, чтобы сразу скрыть все эти функции.Я понимаю, что идея заключалась в том, чтобы передать ясную связь между внутренними классами компонентов и классами общего пользования, но это все еще кажется странным.

Я уверен, что, вероятно, есть и другие вещи, которыездесь также можно улучшить.

Одной из рассмотренных альтернатив было определение определений функций управления непосредственно (в частном порядке) для открытых классов, а затем friend всех внутренних классов / функций, которые должны их использовать., но это кажется еще хуже и гораздо более хрупким.

Есть ли лучший способ для достижения такого рода вещи?

...