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

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

Ответы [ 8 ]

13 голосов
/ 14 июля 2009

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

4 голосов
/ 14 июля 2009

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

3 голосов
/ 14 июля 2009

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

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

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

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

2 голосов
/ 14 июля 2009

Обычно это относится к самому классу, но в некоторой степени оно также должно зависеть от вашего определения «действительный» Например, рассмотрим класс System.IO.FileInfo. Это действительно, если это относится к файлу, который больше не существует? Откуда это знать?

1 голос
/ 14 июля 2009

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

0 голосов
/ 14 июля 2009

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

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

0 голосов
/ 14 июля 2009

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

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

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

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

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

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

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

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

0 голосов
/ 14 июля 2009

Это зависит.

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

Однако иногда бывают случаи, когда это не представляется возможным или нежелательным.

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...