Чаще всего существует необходимость в совместном использовании одних и тех же данных между различными объектами / классами. Я знаю несколько разных способов сделать это:
Иногда такой обмен данными приводит к разработке со слабой инкапсуляцией этих общих данных. Две общие ситуации, которые возникают довольно часто, следующие:
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;
};
Те же вопросы, что и в примере с конечным автоматом …
Вы можете добиться дополнительной защиты, сделав 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() {}
Вы можете использовать идиому 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);
}
};