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