C ++ обмен данными между объектами / классами, реализующими один и тот же интерфейс - PullRequest
0 голосов
/ 21 мая 2018

Чаще всего существует необходимость в совместном использовании одних и тех же данных между различными объектами / классами.Мне известно о нескольких различных способах сделать это:

  1. Глобальные переменные: ненавидят большинство и по правильным причинам.
  2. Синглтоны: это обеспечивает ограниченный контроль над тем, кто может или не можетизмените данные, по крайней мере, для проблемы, описанной ниже.
  3. Внедрение зависимостей: та же проблема, что и для синглетов.

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

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

class SharedData
{
public:
double GetVar() const {return var;}
bool GetFlag() const {return flag;}
void SetVar(double in_var) {var = in_var;}
void SetFlag(bool in_flag) {flag = in_flag;}

private:
double var;
bool flag;
};

class StateIface
{
public:
virtual void Run(SharedData* in_shared_data) = 0;
};

class ConcreteStateA : public StateIface
{
virtual void Run(SharedData* in_shared_data) final;
};

class ConcreteStateB : public StateIface
{
virtual void Run(SharedData* in_shared_data) final;
};

Здесь конкретные реализациинапример, ConcreteStateA потребуется доступ к SharedData, например, чтобы получить / установить определенные данные, возможно, использовать эту информацию для принятия решения о переходе состояния и т. д. Как и в примере выше, мы могли бы объявить SharedData как класс и предоставить средства доступа/ мутаторов.Или мы могли бы просто объявить SharedData как структуру.Однако в обоих случаях конкретные реализации смогут изменять любой параметр в пределах SharedData.Например, предположим, что ConcreteStateA не имеет ничего общего с flag и, следовательно, не должно быть в состоянии изменить его.Однако с данным интерфейсом мы не можем контролировать это поведение.И ConcreteStateA, и ConcreteStateB имеют доступ ко всем данным и могут получить / установить любой параметр.Есть ли лучший дизайн / решение для этой проблемы?Тот, который предлагает больше защиты для общих данных.Или можем ли мы каким-то образом применить ограничение, согласно которому определенный ConcreteState может изменять только определенные параметры SharedData, в то же время реализуя общий StateInterface?

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

class SubroutineInterface
{
public:
virutal void DoSubroutine(SharedData* in_shared_data) = 0;
}

class ConcreteSubroutine : public SubroutineInterface
{
public:
virutal void DoSubroutine(SharedData* in_shared_data) final;
};

Те же вопросы, что и в примере конечного автомата ...

Ответы [ 2 ]

0 голосов
/ 21 мая 2018

Вы можете использовать идиому PassKey, что-то вроде:

class SharedData
{
public:
    class FlagKey
    {
        friend class ConcreteStateA;
        // List here classes which can modify Flag
    private:
        FlagKey() {}
        FlagKey(const FlagKey&) = delete;
    };

    class VarKey
    {
        friend class ConcreteStateB;
        // List here classes which can modify Var
    private:
        VarKey() {}
        VarKey(const FlagKey&) = delete;
    };

public:
    double GetVar() const {return var;}
    bool GetFlag() const {return flag;}
    void SetVar(VarKey, double in_var) {var = in_var;}
    void SetFlag(FlagKey, bool in_flag) {flag = in_flag;}
private:
    double var = 0;
    bool flag = false;
};

А затем:

class ConcreteStateA : public StateIface
{
public:
    virtual void Run(SharedData& data) final {
        // data.SetVar({}, 0); // error: calling a private constructor of class 'VarKey'
        data.SetFlag({}, false);
    }
};
0 голосов
/ 21 мая 2018

Вы можете получить дополнительную защиту, установив SharedData непрозрачный тип

library.h

class SharedData;

void foo(SharedData*);

class bar {
public:
  void method(SharedData*);
};

library_private.h

class SharedData {
public:
  int x;
};

library.cpp

#include "library.h"
#include "library_private.h"

void foo(SharedData* d) {
  d->x = 0;
}

void bar::method(SharedData* d) {
  d->x = 1;
}

Таким образом, только файлы cpp, включая library_private.h, имеют доступ к интерфейсу SharedData, но экземпляры SharedData по-прежнему могут передаваться по всему проекту..

Единственная сложная задача - управлять временем жизни SharedData, так как большую часть времени вам придется выделять SharedData на кучу, а к умным указателям нужно будет прикреплять пользовательские удалители.Таким образом, непрозрачные типы часто оборачиваются в некоторый тип оболочки для управления RAII.

Примерно так:

в заголовке:

class SharedDataImpl;
class SharedData {
public:
  SharedData();
  ~SharedData();

  SharedDataImpl* get() {
    return impl_.get();
  }
private:
  std::unique_ptr<SharedDataImpl> impl_;
};

в .cpp:

SharedData() 
  : impl_(std::make_unique<SharedDataImpl>()) {}

~SharedData() {}
...