Мой вопрос: Как я могу проектировать описанные классы лучше, не беспокоясь о целостности данных?
У меня есть класс Player-Container и Player. Контейнер Player является более или менее централизованным «Контейнером данных» проигрывателя для множества потоков, у которых у всех есть указатель на этот класс. Библиотека TBB предлагает механизм блокировки только для чтения и записи. У меня есть псевдокод, отражающий мой текущий дизайн:
Class: Player-Container
Method: CreatePlayer, RemovePlayer , LoginUser, VerifyUser
Class: Player
Method: None its a pure data container
Теперь для меня, чем больше я занимаюсь разработкой, кажется неправильным иметь эти функциональные возможности в «Top-Level-Container», но я, кажется, не могу обойти это, потому что контейнер будет гарантировать, что я могу заблокировать объект, чтобы никакие параллельные доступы не могли изменить пользователь (например, пользователь создает что-то и отправляет этот запрос дважды, а ресурсы сокращаются вдвое, а не один раз).
Что бы я хотел иметь:
Class: Player-Container
Method: CreatePlayer, RemovePlayer
Class: Player
Method: LoginUser, VerifyUser,....
Моя проблема, как мне добиться этого и при этом сохранить целостность данных, я должен просто использовать hash_map в качестве «индекса» и создать блокировку уровня «Player»? Было бы круто иметь некоторые входные данные и предложения. Больше всего меня беспокоит то, что PlayerContainer на самом деле нужно так много знать о классе проигрывателя, что если я изменю атрибут, мне придется много перекодировать в контейнере.
Если что-то не понятно, пожалуйста, спросите.
Похоже, у вас действительно есть два вопроса здесь, дизайн & запирание, и здесь довольно явно присутствует некоторое конструктивное напряжение вокруг герметизации и сцепления.
От пользователя API, конечно, приятно иметь дело только с классом Player-Container для добавления / удаления / входа игроков.
Я бы подумал о том, чтобы сохранить фронтальные методы в Player-Container для простоты. Однако я также, вероятно, добавил бы вспомогательные классы или вспомогательные функции для их реализации, чтобы гарантировать, что внутри классов Player-Container было очень мало кода. Вы также можете добавить их как методы в класс проигрывателя, если захотите.
Если вы не читали Эта статья от Скотта Мейера о том, где поставить функцию, это, вероятно, стоит вашего времени.
Второй вопрос касается блокировок, то есть куда должны идти блокировки на контейнере или классе игрока.
Здесь есть варианты, но помните, что вам нужно синхронизировать как контейнер, так и данные в отдельном классе. Поэтому, если вы используете параллельный контейнер для удержания игроков и избегаете тяжелой блокировки класса контейнера игрока, вам обязательно нужно будет синхронизировать классы игроков, особенно если вы разрешаете нескольким потокам работать с одним игроком.
Если вы сделаете это, позаботьтесь о том, чтобы иметь больше смысла блокировать не отдельные методы проигрывателя, а весь проигрыватель, чтобы убедиться, что вы не чередуете несовместимые функции, т.е. вы действительно не хотите LoginUser будет работать одновременно с RemoveUser на одном и том же пользователе, вы, вероятно, захотите, чтобы они сериализовались и запускались один за другим.
Я не знаю об особенностях библиотеки TBB, но в целом вы могли бы создать что-то вроде класса SharedObject<Player>
выставлять Player
экземпляры вместе с объектом блокировки (например, мьютексом чтения / записи), доступ к которому можно получить исключительно с помощью объекта SharedObjectAccessor, который имеет дело с правильным применением блокировки.
template<typename T>
class SharedObjectAccessor;
template<typename T>
class SharedObject
{
public:
SharedObject(const T& initial = T())
: managedObject(initial)
{}
private:
friend class SharedObjectAccessor<T>;
const T& read() const { return managedObject; }
T& write() { return managedObject; }
ReadWriteMutex lock;
T managedObject;
};
template<typename T>
class SharedObjectAccessor
{
public:
SharedObjectAccessor(SharedObject<T>& ref)
: sharedObject(ref)
{}
~SharedObjectAccessor() { sharedObject.lock.unlock(); }
const T& get() const
{
sharedObject.lock.lock_read();
return sharedObject.read();
}
T& set()
{
sharedObject.lock.lock_write();
return sharedObject.write();
}
private:
mutable SharedObject<T>& sharedObject;
};