Скрытие полей структуры

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

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

#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 освобождаем память. Функция printName имитирует некоторую работу с состоянием - вывод поля структуры Account на консоль.

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

#include "account.h"

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

Хотя этот пример прекрасно работает. Но что, если мы обратимся на клиенте к полям структуры:

#include <stdio.h>
#include "account.h"

int main(void)
{
    struct Account* acc = createAccount("tom smith", "qwerty");

    printf("%s\n", acc->password); // обращаемся к полю password

    deleteAccount(acc);
}

Этот код прекрасно работает. При данной организации клиент может напрямую обращаться к полям структуры состояния. Однако это может быть нежелательно - клиент может нежелательным образом изменить состояние. Кроме того, это девальвирует API модуля. Поскольку если мы можем обращаться напрямую к полям состояния, зачем нам API?

В ряде языков программирования есть приватные переменные в классах. В Си мы также можем эмулировать подобную возможность, чтобы из внешнего кода нельзя было обращаться к полям структуры. И тут есть два варианта. Можно просто в заголовочном файле просто объявить структуру без реализации, либо объявить в заголовочном файле указатель на структуру. Рассмотрим оба случая. И сначла пропишем реализацию структуры Account непосредственно в файле модуля account.c:

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

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

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;
}

Объявление структуры без определения

Первый способ инкапсуляции полей структуры представляет объявление структуры без ее определения в API модуля. Так, изменим API в файле account.h

struct Account; // только объявление структуры
struct Account* createAccount(char* username, char* pass);
void printName(struct Account* acc);
void deleteAccount(struct Account* acc);

Здесь указано только объявлена структура без реализации. Теперь из кода клиента мы не сможем напрямую обращаться к полям структуры:

#include <stdio.h>
#include "account.h"

int main(void)
{
    struct Account* acc = createAccount("tom smith", "qwerty");

    printf("%s\n", acc->password); // ! Ошибка

    deleteAccount(acc);
}

При попытке обратиться к полям структуры на клиенте мы получим ошибку, поскольку клиент не знает реализацию структуры Account.

Объявление указателя на структуру

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

typedef struct Account* UserAccount;    // указатель на структуру
struct Account* createAccount(char* username, char* pass);
void printName(struct Account* acc);
void deleteAccount(struct Account* acc);

Здесь только определен указатель на структуру, в качестве псевдонима которого выступает тип UserAccount. Но из этого определения клиент опять же не узнает о содержимом структуры. На клиенте в файле app.c мы могли бы ссылаться на указатель структуры как на тип UserAccount

#include <stdio.h>
#include "account.h"

int main(void)
{
    UserAccount acc = createAccount("Tom Smith", "qwerty");
    
    printf("%s\n", acc->password); // ! Ошибка

    deleteAccount(acc);
}

Но при попытке обратиться к отдельным полям структуры мы опять же столкнемся ошибкой. И таким образом, взаимодействовать со структурой мы можем только через API модуля.

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