C # / Объектно-ориентированный дизайн - поддержание правильного состояния объекта

2

При разработке класса должна ли логика поддерживать правильное состояние в классе или вне его? То есть, должны ли свойства вызывать исключения из недопустимых состояний (т.е. Значение вне диапазона и т.д.) Или эта проверка должна выполняться при создании/изменении экземпляра класса?

  • 0
    Посмотрите этот вопрос для получения дополнительной информации: stackoverflow.com/questions/88541/…
Теги:
state
oop

8 ответов

13
Лучший ответ

Он принадлежит классу. Ничто, кроме самого класса (и каких-либо помощников, которому он делегирует), должно знать или иметь отношение к правилам, которые определяют действительное или недопустимое состояние.

  • 0
    Спасибо за ваш ответ. В этом случае, я бы окружил экземпляры try ... catch или fail "изящно"? Например: try {Type myType = new Type (aValue); } catch (InvalidException e) {..} или установить какое-либо "безопасное" значение по умолчанию?
  • 3
    Нет, это не изящная ошибка, это ложь. Оставьте исключение в покое, пока вы не сможете с этим что-то сделать .
Показать ещё 1 комментарий
4

Да, при настройке свойства должны проверять допустимые/недопустимые значения. Это для чего.

3

Нельзя помещать класс в недопустимое состояние, вне зависимости от кода вне него. Это должно дать понять.

С другой стороны, код, находящийся за его пределами, по-прежнему несет ответственность за правильное использование класса, поэтому часто бывает целесообразно дважды проверять его. Методы класса могут бросать ArgumentException, если передаются что-то, что им не нравится, и код вызова должен гарантировать, что этого не произойдет, если у вас есть правильная логика для проверки ввода и т.д.

Существуют также более сложные случаи, когда в системе существуют разные "уровни" клиента. Примером является ОС - приложение работает в "Пользовательском режиме" и должно быть неспособно привести ОС в недопустимое состояние. Но драйвер работает в режиме "Ядро" и отлично способен повредить состояние ОС, потому что он входит в состав команды, которая отвечает за реализацию сервисов, используемых приложениями.

Этот тип двухуровневой компоновки может возникать в объектных моделях; могут быть "внешние" клиенты модели, которые видят только действительные состояния и "внутренние" клиенты (плагины, расширения, дополнения), которые должны быть в состоянии видеть, что иначе считалось бы "недействительными" состояниями, потому что они играют определенную роль в реализации государственных переходов. Определение invalid/valid отличается в зависимости от роли, которую играет клиент.

  • 1
    Так что же произойдет, если у вас есть требование проверки, что у объекта, у которого есть виджет, также должен быть гаджет, и наоборот. Поскольку вы не можете назначить оба элемента одновременно (как правило), вам придется жить с недопустимым состоянием как минимум в течение короткого периода времени. Я думаю, что для большинства целей достаточно, чтобы вы не могли сохранить недопустимое состояние. Эфемерные объекты могут быть недействительными, если они не сохранены.
  • 1
    Это на каком-то гипотетическом языке, где я не могу определить метод, который принимает два аргумента?
Показать ещё 5 комментариев
2

Как правило, это принадлежит самому классу, но в какой-то степени оно также зависит от вашего определения "valid". Например, рассмотрим класс System.IO.FileInfo. Действительно ли это относится к файлу, который больше не существует? Как это знать?

1

Я согласен с @Джо. Обычно это можно найти в классе. Однако я бы не хотел, чтобы аксессоры свойств реализовали логику проверки. Скорее я бы рекомендовал метод проверки для слоя persistence, который должен вызывать, когда объект сохраняется. Это позволяет локализовать логику проверки в одном месте и делать разные варианты действительных/недействительных на основе выполняемой операции сохранения. Если, например, вы планируете удалить объект из базы данных, вам небезразлично, что некоторые из его свойств недействительны? Вероятно, нет - если версии ID и строки такие же, как в базе данных, вы просто продолжаете и удаляете ее. Аналогичным образом, у вас могут быть разные правила для вставок и обновлений, например, некоторые поля могут быть пустыми при вставке, но необходимы для обновления.

0

Класс действительно должен поддерживать допустимые значения. Не имеет значения, введены ли они через конструктор или через свойства. Оба должны отклонять недопустимые значения. Если для параметра конструктора и свойства требуется такая же проверка, вы можете использовать общий закрытый метод для проверки значения как для свойства, так и для конструктора, или вы можете выполнить проверку в свойстве и использовать свойство внутри своего конструктора при настройке локальные переменные. Я бы рекомендовал использовать общий метод проверки лично.

Ваш класс должен генерировать исключение, если он получает недопустимые значения. В целом, хороший дизайн может помочь уменьшить шансы на это.

0

Действительное состояние в классе лучше всего выражать с понятием инвариант класса. Это логическое выражение, которое должно иметь значение true для объектов этого класса.

Подход Дизайн по контракту предполагает, что вы, как разработчик класса C, должны гарантировать, что класс инвариант имеет значение:

  • После построения
  • После вызова общедоступного метода

Это будет означать, что, поскольку объект инкапсулирован (никто не может его изменить, кроме как через вызовы общедоступным методам), инвариант также будет выполняться при вводе любого общедоступного метода или при вводе деструктора (на языках с деструкторами), если они есть.

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

Концепция, отличная от правильной, но дополняющей ее (и, конечно же, принадлежащая нескольким факторам качества программного обеспечения), отличается от надежной. В нашем контексте надежный класс обнаружит, когда вызывается один из его методов, не выполняя предварительные условия метода. В таких случаях, как правило, возникает исключение нарушения утверждения, так что вызывающий абонент знает, что он взорвал его.

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

Классы, принадлежащие общему интерфейсу библиотеки кода, должны быть скомпилированы как надежные, тогда как внутренние классы могут быть протестированы как надежные, но затем запускаться в выпущенном продукте как только правильные, без предварительных проверок. Это зависит от ряда факторов, и обсуждался в другом месте.

0

Это зависит.

Если проверка проста и может быть проверена с использованием только информации, содержащейся в классе, тогда большую часть времени стоит добавить проверки состояния в класс.

Однако иногда бывает так, что это действительно невозможно или желательно сделать.

Отличным примером является компилятор. Проверка состояния абстрактных синтаксических деревьев (АСТ), чтобы убедиться, что программа действительна, обычно не выполняется с помощью свойств или конструкторов. Вместо этого проверка обычно выполняется посетителем дерева или серией взаиморекурсивных методов в каком-то "семантическом классе анализа". Однако в любом случае свойства проверяются долго после того, как их значения установлены.

Кроме того, с объектами, используемыми для старого состояния пользовательского интерфейса, обычно это плохая идея (с точки зрения удобства использования), чтобы генерировать исключения, когда установлены недопустимые значения. Это особенно справедливо для приложений, использующих привязку данных WPF. В этом случае вы хотите отображать какую-то модельную обратную связь для клиента, а не бросать исключение.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню