#c #oop #winapi #static-libraries
#c #ооп #winapi #статические библиотеки
Вопрос:
Я разрабатываю библиотеку синтаксического анализа, в которой я анализирую данные и сохраняю их в разных структурах данных. Дизайн библиотеки таков, что в ней будут классы dataProvider, Parser и DataStore. Хранилище данных является элементом класса dataProvider. Потребителю необходимо вызвать функции в dataProvider для анализа файла и извлечения данных из библиотеки.
Теперь, если я предоставлю класс dataProvider, мне нужно также предоставить класс хранилища данных, который предоставляет детали реализации потребителю. Каков альтернативный способ предоставления доступа к функциям класса dataProvider? Должен ли я предоставлять такие функции, как LoadFile, GetRecords и создавать объект dataProvider глобально внутри cpp?
Комментарии:
1. Если ваше решение включает глобальную переменную, то это плохо.
2. Вызывает ли здесь беспокойство то, что кто-то может создать
DataStore
объект и использовать этот объект вне контекста aDataProvider
?3. @DeadMG:
std::cout
тогда это плохое решение? В конце концов, это глобальная переменная.
Ответ №1:
Если пользователю не нужно использовать DataStore
напрямую, его лучше не раскрывать. Вы можете добиться этого, создав абстрактный «интерфейс» DataProvider
, содержащий только общедоступные чисто виртуальные функции. Внутренне имейте a DataProviderImp
, который будет наследоваться от DataProvider
, и содержать все необходимые определения и члены, которые являются частью фактической реализации.
Позвольте пользователю работать только с абстрактным классом. Таким образом, вы перетаскиваете в свой API только минимальные зависимости.
Ответ №2:
Во-первых, сведите к минимуму информацию, которая должна быть раскрыта в заголовках, поэтому используйте прямые объявления, ссылки и указатели, где это возможно (поэтому требуется только прямое объявление), и попробуйте использовать идиому PIMPL, чтобы скрыть реализацию.
Затем вы можете создать другой класс, который является фасадом системы, который закрывает точки входа в функции, и использовать косвенные методы для доступа к содержащимся элементам (например, к элементам данных, доступным извне), таким как дескрипторы и индексы.
Ответ №3:
Если проблема заключается в том, что внешние пользователи могут создавать свои собственные DataStore
объекты, использовать их вне контекста a DataProvider
, и они жалуются, что ваша библиотека не работает, есть простое решение и некоторые не очень простые решения.
Простое решение: документ, который DataProvider
является внешним интерфейсом для вашей библиотеки. Это решение используется в стандартной библиотеке C и в Boost. Интерфейсом к std::map
является файл заголовка <map>
. Файлы реализации, которые создает этот заголовок #includes
, и базовые типы данных, которые <map>
создают его вспомогательные заголовки, — это не ваше дело. Вы должны использовать только std::map
общедоступные интерфейсы. Используйте внутренние типы данных, и вы попадаете в мир неопределенного поведения. Подобные комментарии // The class DataStore is for internal use only. Use it and you will be fired.
могут быть довольно мощным сдерживающим фактором.
Решение, которое не зависит от вышеизложенного: отгородите эти вспомогательные классы, определив их внутри DataProvider
(например, у вас будет class DataProvider::DataStore
) и сделав эти определения классов закрытыми / защищенными DataProvider
. Другой подход заключается в том, чтобы сделать все DataStore
закрытым / защищенным и создать DataProvider
дружественный класс.
Поскольку вы предоставляете статическую библиотеку и заголовки, всегда будут какие-то неприятные хакерские способы получить доступ к вашим базовым данным и методам, независимо от того, как сильно вы пытаетесь их отгородить. В какой-то момент «Это только для внутреннего использования. Убери свои грязные рукавицы!» подход имеет немало достоинств.
Комментарии:
1. Проблема в том, что если хранилище данных является переменной-членом, то я также должен указать заголовок для этого класса, который раскрывает большую часть моей внутренней структуры данных. Лучшее, что я думаю, это, как предложил eran, создать IDataStore интерфейса и создать для него элемент вместо конкретного класса, чтобы я мог предоставить заголовок, который имеет только интерфейс.
2. Если вы скрыли этот элемент от внешних пользователей, сделав его закрытым / защищенным, вас действительно волнует, могут ли внешние пользователи видеть ваши внутренние типы данных? Если вы пишете коммерческий пакет, возможно. Если это не так, в чем проблема? Даже если ваши внешние пользователи не могут получить доступ к вашим внутренним данным, предоставление ваших внутренних представлений может помочь вашим внешним пользователям понять ваш пакет.