Хранение состояния модуля на клиенте

Последнее обновление: 16.09.2023

В определенных ситуациях состояние модуля может полностью зависеть от клиента - внешнего кода, который к этому состоянию обращается. То есть модуль определяет структуру состояния и API для взаимодействия с состоянием, а содержимое и жизненный цикл состояния определяет клиент. При этом модуль хочет скрыть от клиента реализацию API для взаимодействия с состоянием. Например, пользователь создает на некотором сервисе учетную запись. Учетная запись полностью зависит от пользователя, пользователь может удалить ее. Однако интерфейс, API для управления учетной записью определяет сервис или модуль. И детали этого API скрыты от пользователя.

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

Примерная реализация:

/* API модуля (заголовочный файл) */
// структура, которая представляет состояние
struct INSTANCE
{
  int x;
  int y;
};

// создание состояния
struct INSTANCE* createInstance();

// некоторые действия над состоянием
void operateOnInstance(struct INSTANCE* inst);
// удаление состояния
void destroyInstance(struct INSTANCE* inst);

/* Реализация в модуле */
struct INSTANCE* createInstance()
{
  struct INSTANCE* inst;
  inst = malloc(sizeof(struct INSTANCE));
  return inst;
}

void operateOnInstance(struct INSTANCE* inst)
{
  // некоторая работа с состоянием
}

void destroyInstance(struct INSTANCE* inst)
{
  free(inst);
}

/* код клиента */
struct INSTANCE* inst;
inst = createInstance();    // создаем состояние
operateOnInstance(inst);    // работаем с состоянием
destroyInstance(inst);      // работу закончили - удаляем состояние

Таким образом, код становится более сложным. Кроме того, модуль должен определить функции для создания и удаления состояния, а клиент должен не забыть их вызвать. Однако такой подход удобен в многопоточных программах со множеством клиентов, так как каждый клиент хранит свое собственную копию состояния. В тоже время очевидно, что он подойдет не для всех задач. Например, если несколько клиентов (потоков) захотят записать данные в один и тот же файл, то естественно они не смогут это сделать, поскольку файл один. Подобный паттерн еще называют Client-Dependent Instance или Caller-Owned Instance

В качестве небольшого примера определим заголовочный файл account.h, который будет хранить API модуля account:

#define STR_SIZE 16
struct Account
{
    char username[STR_SIZE];
    char password[STR_SIZE];
};

struct Account* createAccount(char* username, char* pass);
void printName(struct Account* acc);
void deleteAccount(struct Account* acc);

Здесь состояние определено в виде структуры Account, которая представляет условную учетную запись и определяет два поля - для хранения соответственно имени пользователя и пароля.

Также API определяет три функции. Функция createAccount принимает логин и пароль пользователя и возвращает указатель на структуру Account. Функция printName получает указатель на структуру Account и выполняет с ней некоторые действия (выводит имя пользователя на консоль). И функция deleteAccount удаляет структуру Account.

В файле account.c реализуем API модуля:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include "account.h"

struct Account* createAccount(char* username, char* pass)
{
  struct Account* acc = malloc(sizeof(struct Account));
  memcpy(acc->username, username, STR_SIZE);
  memcpy(acc->password, pass, STR_SIZE);
  return acc;
}

void printName(struct Account* acc)
{
  if(acc) printf("User Name: %s\n", acc->username);
}

void deleteAccount(struct Account* acc)
{
  if(acc) free(acc);
  acc = 0;
}

В данном случае в функции createAccount выделяем память под структуру Account, а в функции deleteAccount освобождаем память.

В файле app.c определим клиент, который будет хранить объект состояния и обращаться к API модуля account:

#include "account.h"

int main(void)
{
    struct Account* acc = createAccount("tom smith", "qwerty"); // создаем состояние
    printName(acc);     // операции с состоянием
    deleteAccount(acc); // удаляем состояние
}

Пример компиляции и консольный вывод программы:

c:\C>gcc -Wall -pedantic app.c account.c -o app & app
User Name: tom smith

c:\C>
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850