Скажем, мой проект использует библиотеку LibFoo
, которая обеспечивает ее функциональность через несколько классов, например FooA
и FooB
. Теперь существует ряд похожих библиотек (например, LibBar
, который предоставляет BarA
и BarB
), которые предоставляют те же функции, что и LibFoo
, и я хочу, чтобы пользователи моего проекта могли выбрать, какую библиотеку использовать, предпочтительно во время выполнения.
Для этого я создал "оберточный слой", который определяет интерфейсы, которые я ожидаю от библиотек. В моем примере этот слой содержит два интерфейса: IfaceA
и IfaceB
. Затем для каждой библиотеки, которую я хочу поддерживать, я создаю "уровень реализации", который реализует интерфейсы с использованием одной из библиотек.
Теперь моя проблема заключается в том, как хорошо реализовать уровень реализации. Чтобы продемонстрировать свою проблему, рассмотрим, что у нас есть следующие интерфейсы (показаны на С++, но должны быть применимы к аналогичным языкам):
class IfaceA
{
public:
virtual void doSomethingWith(IfaceB& b) = 0;
...
};
class IfaceB
{
...
};
Классы уровня реализации LibFoo
будут содержать объекты из соответствующих классов LibFoo
. Операции в интерфейсах должны быть реализованы с использованием этих объектов. Следовательно (извините меня за ужасные имена):
class ConcreteAForFoo : public IfaceA
{
public:
void doSomethingWith(IfaceB& b) override
{
// This should be implemented using FooA::doSomethingWith(FooB&)
}
private:
FooA a;
};
class ConcreteBForFoo : public IfaceB
{
public:
FooB& getFooB() const
{
return b;
}
private:
FooB b;
};
Проблема заключается в реализации ConcreteAForFoo::doSomethingWith
: его параметр имеет тип IfaceB&
, но мне нужно получить доступ к деталям реализации ConcreteBForFoo
, чтобы иметь возможность правильно реализовать метод. Единственный способ, которым я нашел это, - использовать уродливые понижения по всему месту:
void doSomethingWith(IfaceB& b) override
{
assert(dynamic_cast<ConcreteBForFoo*>(&b) != nullptr);
a.doSomethingWith(static_cast<ConcreteBForFoo&>(b).getFooB());
}
Поскольку отбрасывание обычно считается запахом кода, я не могу не думать, что должен быть лучший способ сделать это. Или я проектирую это неправильно для начала?
TL; DR
Учитывая уровень взаимозависимых интерфейсов (в том виде, в котором методы в одном интерфейсе получают ссылки на другие интерфейсы). Как реализации этих интерфейсов могут совместно использовать сведения о деталях без downcasting или раскрытия этих деталей в интерфейсах?