Убедитесь, что все не-`const` члены данных имеют одинаковый уровень доступа
Причина
Предотвращение логической путаницы, ведущей к ошибкам. Если не-const члены данных имеют разные уровни доступа, тип противоречит сам себе в том, что он пытается делать. Является ли он типом, поддерживающим инвариант, или просто набором значений?
Обсуждение
Ключевой вопрос: какой код отвечает за поддержание осмысленного/корректного значения этой переменной?
Существует ровно два вида членов данных:
- A: Те, которые не участвуют в инварианте объекта. Любая комбинация значений этих членов допустима.
- B: Те, которые участвуют в инварианте объекта. Не каждая комбинация значений осмысленна (иначе инварианта не было бы). Поэтому весь код с доступом на запись к этим переменным должен знать об инварианте, понимать семантику и знать (и активно реализовывать и поддерживать) правила сохранения корректности значений.
Члены данных категории A должны быть просто public (или, реже, protected, если их должны видеть только производные классы). Они не нуждаются в инкапсуляции. Весь код системы может видеть и изменять их.
Члены данных категории B должны быть private или const. Это объясняется важностью инкапсуляции. Сделать их не-private и не-const означало бы, что объект не может контролировать своё собственное состояние: неограниченное количество кода за пределами класса должно было бы знать об инварианте и участвовать в его точном поддержании — если бы эти члены были public, это был бы весь вызывающий код, использующий объект; если бы они были protected — весь код текущих и будущих производных классов. Это ведёт к хрупкому и тесно связанному коду, который быстро становится кошмаром для сопровождения. Любой код, случайно устанавливающий члены данных в недопустимую или неожиданную комбинацию значений, испортит объект и все последующие его использования.
Большинство классов являются либо полностью A, либо полностью B:
По соглашению объявляйте такие классы как struct, а не class
- Полностью публичные: если вы пишете агрегированный набор переменных без инварианта по этим переменным, все переменные должны быть
public. - Полностью приватные: если вы пишете тип, поддерживающий инвариант, все не-
constпеременные должны быть private — они должны быть инкапсулированы.
Исключение
Иногда классы будут смешивать A и B, обычно в целях отладки. Инкапсулированный объект может содержать, например, не-const инструментарий отладки, который не является частью инварианта и поэтому относится к категории A — он не является частью значения объекта или его значимого наблюдаемого состояния. В этом случае части A следует обращаться как с A (делать public или, реже, protected, если они должны быть видны только производным классам), а части B всё равно следует обращаться как с B (private или const).
Контроль
Помечать любой класс, имеющий не-const члены данных с разными уровнями доступа.