Как обеспечить разные интерфейсы для объекта (оптимально) - PullRequest
0 голосов
/ 09 апреля 2019

Мне нужен способ предоставления разных интерфейсов из одного объекта.
Например.Пользователь 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), я даже предпочитаю этого не делать, но я не могу использовать необработанные указатели, а умные указатели дают ненужные служебные и системные вызовы.

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

Ответы [ 2 ]

1 голос
/ 09 апреля 2019

Что вам по сути нужно, так это общие данные между двумя объектами.Наследование не очень хороший выбор для этого, потому что вы не только не нуждаетесь в 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 соответственно.Последнее является более «ориентированным на данные» программированием, которое в наши дни пользуется большой популярностью, в отличие от более традиционного «объектно-ориентированного», что я и решил продемонстрировать.

0 голосов
/ 10 апреля 2019

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

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();
}

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

...