Как предоставить разные интерфейсы объекту (оптимально)

Мне нужен способ предоставить разные интерфейсы из одного объекта.
Например. Пользователь 1 должен иметь возможность звонить Foo::bar(), а пользователь 2 должен иметь возможность звонить Foo::baz(), но пользователь 1 не может звонить Foo::baz() и, соответственно, пользователь 2 не может звонить Foo::bar().

Мне удалось это сделать, но я не думаю, что это оптимально.

class A
{
    public:
    virtual void bar() = 0;
    virtual ~A() = 0;
};

class B
{
    public:
    virtual void baz() = 0;
    virtual ~B() = 0;
};

class Foo : public A, public B
{
    public:
    Foo() = default;
    void baz() override;
    void bar() override;

};

class Factory
{
    public:
    Factory()
    {
        foo = std::make_shared<Foo>();
    }
    std::shared_ptr<A> getUserOne()
    {
        return foo;
    }

    std::shared_ptr<B> getUserTwo()
    {
        return foo;
    }

    private:
    std::shared_ptr<Foo> foo;
};

Есть ли лучший способ добиться этого. Возможно, с объектами-обертками. Мне действительно не нужно выделять этот объект foo с помощью new(std::make_shared). Я даже предпочитаю этого не делать, но я не могу использовать необработанные указатели, а интеллектуальные указатели дают ненужные накладные расходы и системные вызовы.

Обновлено: я постараюсь привести пример.
Есть машина. Первый пользователь — водитель. Он может управлять рулем, ускоряться или использовать тормоза. Второй пользователь — пассажир, и он может, например, управлять радио. Я не хочу, чтобы пассажир мог пользоваться тормозами, а водитель — радио.
Кроме того, они оба находятся в машине, поэтому действия первого пользователя повлияют на второго пользователя и наоборот.

Кто такие пользователь1 и пользователь2? Как они вообще попадают внутрь одной и той же программы? Как вы решаете, что user1 является В самом деле user1, а не user2, притворяясь?

Useless 09.04.2019 14:03

Прочтите о ху проблема. Ваши вопросы читаются как «я сделал xy, но я думаю, что это не оптимально, что еще мне делать?», но это не на 100% ясно, какую проблему вы пытаетесь решить. Что такое «пользователи»? Зачем вам другой доступ?

463035818_is_not_a_number 09.04.2019 14:21

Позвольте мне привести пример. Представьте, что пользователи — это два разных потока. Один из них должен иметь возможность заполнять некоторые полезные данные в классе, а другой должен иметь возможность их читать, очищать, возможно, блокировать другого пользователя. Другими словами - один из пользователей должен иметь возможность использовать методы объекта, а другой должен иметь возможность управлять объектом.

Petar Velev 09.04.2019 14:33
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
60
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

По сути, вам нужны общие данные между двумя объектами. Наследование не очень хороший выбор для этого, потому что вам не только не нужны is A отношения, но вы явно хотите их избежать. Поэтому состав - это ваш ответ, тем более, что у вас есть фабрика:

class Data
{
public:
    void bar();
    void baz();
};

Тогда вместо наследования вы должны использовать композицию:

class A
{
public:
    A(Base *base) : mBase(base) {}

    void bar() { mBase->bar(); }

private:
    Base *mBase = nullptr;
};

//class B would be the same only doing baz()

Наконец Factory:

class Factory
{
public:
    A *getUserOne() { return &mA; }
    B *getUserTwo() { return &mB; }

private:
    Base mBase;
    A mA(&mBase);
    B mB(&mBase);
};

Пара моментов об этом решении. Пока он не размещается в куче, вам нужно будет поддерживать жизнь Factory, пока есть его пользователи. По этой причине использование std::shared_ptr, как в OP, может быть идеей умный. :-) Но это, конечно, связано со стоимостью атомарного подсчета ссылок.

Во-вторых, A никак не связано с B. Это задумано и, в отличие от исходного решения, не допускает dynamic_cast между A и B.

Наконец, где будет реализация, зависит от вас. Вы можете иметь все это в Data, а A и B просто вызывать его (как показано выше), но вы также можете превратить Data просто в struct, содержащий только ваши данные, и реализовать свои методы в A и B соответственно. Последнее является более «ориентированным на данные» программированием, которое в наши дни пользуется большой популярностью, в отличие от более традиционного «объектно-ориентированного», которое я решил продемонстрировать.

Вы можете объявить свои данные отдельно

struct Data
{
    /* member variables */
};

Иметь класс интерфейса, способный манипулировать указанными данными, все члены будут защищены

class Interface
{
protected:
    Interface(Data &data) : m_data{data} {}

    void bar() { /* implementation */ }
    void baz() { /* implementation */ }

    Data &m_data;
};

Имеют производные классы, которые делают общедоступными конкретные члены

class A : private Interface
{
public:
    A(Data &data) : Interface{data} {}
    using Interface::bar;
};

class B : private Interface
{
public:
    B(Data &data) : Interface{data} {}
    using Interface::baz;
};

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

class Admin : private Interface
{
public:
    Admin(Data &data) : Interface{data} {}
    using Interface::bar;
    using Interface::baz;
};

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

Пример кода с использованием этой модели:

void test()
{
    Data d{};

    auto a = A{d};
    a.bar();
    // a.baz is protected so illegal to call here

    auto b = B{d};
    b.baz();
    // b.bar is protected so illegal to call here

    auto admin = Admin{d};
    admin.bar();
    admin.baz();
}

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

Другие вопросы по теме