Информация об изменениях

Сообщение Re: Интерфейсы и реализации от 06.03.2024 0:57

Изменено 06.03.2024 1:24 rg45

Re: Интерфейсы и реализации
Здравствуйте, Marty, Вы писали:

M>Обычно, если реализация зависит от платформы, не парюсь и делаю абстрактный интерфейс с чисто виртуальными функциями, делаю реализацию под target-платформу, а фабрика создаёт объект в куче и возвращает указатель на интерфейс, голенький или shared.


M>Из плюсов — можно при необходимости даже в рамках одной платформы и в рамках одного приложения сделать разные реализации и при необходимости использовать ту или иную реализацию, с возможностью выбора в рантайме.


Ну вот тут желательно сразу определиться с потребностями. Переносимость на различные платформы — это одно, а возможность выбора реализации в рантайме — совсем другое. Если тебе действительно нужно переключаться между реализациями в рантайме, то ты на правильном пути.

M>В общем, как бы и на елку залезть, и жопу не ободрать?

M>Есть идеи, как сделать шоколадно?

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

template <typename T>
concept Cursor = std::default_initializable<T> && BooleanTestable<T>;

template <typename T>
concept Window =
requires (const T& w) {
   // Constant properties:
   {w.GetSize()} -> std::same_as<Vector<2, int>>;
   {w.GetPos()} -> std::same_as<Vector<2, int>>;
   {w.GetCursor()} -> Cursor;
} &&
requires (T& w, Vector<2, int> v, Cursor auto cursor) {
   // Modifying operations:
   w.SetSize(v);
   w.SetPos(v);
   {w.SetCursor(cursor)} -> Cursor;
   {w.createStockCursor(std::declval<EStockCursor>())} -> Cursor;
};


Потом набрасываешь классы реализаций и тут же можно проверять их на соответствие концептам:

class WinWindow
{
public:
   GetSize() . . .
   SetSize() . . .
   GetCursor() . . .
   SetCursor() . . .
};

// Concept check:
static_assert(Window<WinWindow>);
Re: Интерфейсы и реализации
Здравствуйте, Marty, Вы писали:

M>Обычно, если реализация зависит от платформы, не парюсь и делаю абстрактный интерфейс с чисто виртуальными функциями, делаю реализацию под target-платформу, а фабрика создаёт объект в куче и возвращает указатель на интерфейс, голенький или shared.


M>Из плюсов — можно при необходимости даже в рамках одной платформы и в рамках одного приложения сделать разные реализации и при необходимости использовать ту или иную реализацию, с возможностью выбора в рантайме.


Ну вот тут желательно сразу определиться с потребностями. Переносимость на различные платформы — это одно, а возможность выбора реализации в рантайме — совсем другое. Если тебе действительно нужно переключаться между реализациями в рантайме, то ты на правильном пути.

M>В общем, как бы и на елку залезть, и жопу не ободрать?

M>Есть идеи, как сделать шоколадно?

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

template <typename T>
concept Cursor = std::default_initializable<T> && BooleanTestable<T>;

template <typename T>
concept Window =
requires (const T& w) {
   // Constant properties:
   {w.GetSize()} -> std::same_as<Vector<2, int>>;
   {w.GetPos()} -> std::same_as<Vector<2, int>>;
   {w.GetCursor()} -> Cursor;
} &&
requires (T& w, Vector<2, int> v, Cursor auto cursor) {
   // Modifying operations:
   w.SetSize(v);
   w.SetPos(v);
   {w.SetCursor(cursor)} -> Cursor;
   {w.createStockCursor(std::declval<EStockCursor>())} -> Cursor;
};


Потом набрасываешь классы реализаций и тут же можно проверять их на соответствие концептам:

class WinWindow
{
public:
   GetSize() . . .
   SetSize() . . .
   GetCursor() . . .
   SetCursor() . . .
};

// Concept check:
static_assert(Window<WinWindow>);


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