Я часто сталкиваюсь с этой проблемой, особенно в Java, даже если я думаю, что это общая проблема ООП. То есть: поднятие исключения выявляет проблему дизайна.
Предположим, что у меня есть класс, который имеет String name
поле и String surname
поле.
Затем он использует эти поля, чтобы составить полное имя человека, чтобы отобразить его в каком-то документе, скажем, в счете.
public void String name;
public void String surname;
public String getCompleteName() {return name + " " + surname;}
public void displayCompleteNameOnInvoice() {
String completeName = getCompleteName();
//do something with it....
}
Теперь я хочу усилить поведение моего класса, выдавая ошибку, если displayCompleteNameOnInvoice
вызывается до того, как имя было присвоено. Это кажется хорошей идеей, не так ли?
Я могу добавить код, вызывающий исключение, в getCompleteName
метод. Но таким образом я нарушаю «неявный» контракт с пользователем класса; в общем случае геттеры не должны генерировать исключения, если их значения не установлены. Хорошо, это не стандартный метод получения, поскольку он не возвращает ни одного поля, но с точки зрения пользователя различие может быть слишком тонким, чтобы об этом думать.
Или я могу выбросить исключение изнутри displayCompleteNameOnInvoice
. Но чтобы сделать это, я должен проверить напрямую name
или surname
поля, и, делая это, я бы нарушил абстракцию, представленную getCompleteName
. Именно этот метод отвечает за проверку и создание полного имени. На основании других данных он может даже решить, что в некоторых случаях этого достаточно surname
.
Таким образом, единственная возможность, по-видимому, состоит в том, чтобы изменить семантику метода getCompleteName
на то composeCompleteName
, что предполагает более «активное» поведение и, соответственно, возможность генерировать исключение.
Это лучшее дизайнерское решение? Я всегда ищу лучший баланс между простотой и правильностью. Есть ли ссылка на дизайн для этой проблемы?
источник
displayCompleteNameOnInvoice
можете просто вызвать исключение, еслиgetCompleteName
вернетесьnull
, не так ли?Ответы:
Не позволяйте вашему классу быть построенным без назначения имени.
источник
Ответ, предоставленный DeadMG, в значительной степени пригвоздит его, но позвольте мне сформулировать его немного иначе: избегайте объектов с недопустимым состоянием. Объект должен быть «целым» в контексте задачи, которую он выполняет. Или не должно существовать.
Поэтому, если вы хотите плавности, используйте что-то вроде Pattern Builder . Или что-нибудь еще, что является отдельным воплощением объекта в конструкции, в отличие от «реальной вещи», где последний гарантированно имеет действительное состояние и является тем, который фактически выставляет операции, определенные в этом состоянии (например
getCompleteName
).источник
So if you want fluidity, use something like the builder pattern
- текучесть или неизменность (если только это не то, что вы имеете в виду). Если кто-то хочет создать неизменяемые объекты, но ему нужно отложить установку некоторых значений, то шаблон, который следует выполнить, состоит в том, чтобы установить их один за другим на объекте-построителе и создать неизменяемый объект только после того, как мы собрали все значения, необходимые для правильного состояние ...Не иметь объект с недопустимым состоянием.
Цель конструктора или компоновщика состоит в том, чтобы установить состояние объекта в нечто согласованное и пригодное для использования. Без этой гарантии возникают проблемы с неправильно инициализированным состоянием объектов. Эта проблема усложняется, если вы имеете дело с параллелизмом, и что-то может получить доступ к объекту, прежде чем вы полностью закончите его настройку.
Итак, вопрос для этой части: «Почему вы допускаете, чтобы имя или фамилия были нулевыми?» Если это не то, что допустимо в классе для правильной работы, не позволяйте этому. Иметь конструктор или конструктор, который правильно проверяет создание объекта и, если это не так, поднимает проблему в этой точке. Вы также можете использовать одну из существующих
@NotNull
аннотаций , чтобы помочь понять, что «это не может быть нулем», и применять ее при кодировании и анализе.С этой гарантией становится намного проще рассуждать о коде, о том, что он делает, и не нужно бросать исключения в нечетных местах или ставить чрезмерные проверки вокруг функций-получателей.
Получатели, которые делают больше.
На эту тему есть немного. У вас есть Как много логики в Геттерз и что должно быть разрешено в добытчиками и сеттеров? отсюда и геттеры и сеттеры, выполняющие дополнительную логику над переполнением стека. Это проблема, которая возникает снова и снова в дизайне класса.
Суть этого исходит от:
И ты прав. Они не должны. Они должны выглядеть «глупыми» по отношению к остальному миру и делать то, что ожидается. Использование слишком большого количества логики приводит к проблемам, когда нарушается закон наименьшего удивления. И давайте посмотрим правде в глаза, вы действительно не хотите обернуть геттеры,
try catch
потому что мы знаем, как мы любим делать это в целом.Существуют также ситуации, когда вы должны использовать
getFoo
метод, такой как инфраструктура JavaBeans, и когда у вас есть что-то из EL, вызывающего, ожидающего получить bean-компонент (так, чтобы<% bar.foo %>
фактически вызыватьgetFoo()
в классе - оставляя в стороне 'bean-компонент, выполняющий композицию или должен что оставить на усмотрение? », потому что можно легко придумать ситуации, когда один или другой может быть правильным ответом)Поймите также, что для заданного получателя возможно быть указанным в интерфейсе или быть частью ранее открытого публичного API для класса, который подвергается рефакторингу (в предыдущей версии было только что возвращено «completeName», и рефакторинг прервался это в два поля).
В конце дня...
Делайте то, о чем легче рассуждать. Вы потратите больше времени на поддержку этого кода, чем на его разработку (хотя чем меньше времени вы тратите на разработку, тем больше времени вы тратите на его обслуживание). Идеальный выбор - спроектировать его таким образом, чтобы он не занимал столько времени на обслуживание, но не сидите и не думайте об этом в течение нескольких дней - если только это действительно не будет дизайнерским выбором, который будет иметь последствия спустя несколько дней. ,
Попытка придерживаться смысловой чистоты получающего существа
private T foo; T getFoo() { return foo; }
в какой-то момент вызовет у вас проблемы. Иногда код просто не вписывается в эту модель, и те сложности, через которые приходится пытаться сохранить его таким образом, просто не имеют смысла ... и в конечном итоге усложняют его проектирование.Иногда принимайте, что идеалы дизайна не могут быть реализованы так, как вы хотите их в коде. Руководящие принципы - это руководящие принципы, а не кандалы.
источник
surname
илиname
быть нулевым является действительным состоянием для объекта, а затем обработать его соответствующим образом . Но если это недопустимое состояние, вызывающее методы в классе для генерирования исключений, следует предотвратить его нахождение в этом состоянии с момента инициализации. Вы можете обойти незавершенные объекты, но обойти недопустимые объекты проблематично. Если нулевая фамилия является исключительной, попытайтесь предотвратить ее раньше, чем позже.Я не Java-парень, но это похоже на оба ограничения, которые вы представили.
getCompleteName
не выдает исключение, если имена не инициализированы, иdisplayCompleteNameOnInvoice
делает.источник
getCompleteName()
не нужно заботиться о том, действительно ли свойство хранится или вычисляется на лету.getCompleteName
, что отвратительно.getCompleteName()
в остальной части класса, или даже в других частях программы. В некоторых случаях возвращениеnull
может быть именно тем, что требуется. Но в то время как есть ОДИН метод, спецификация которого «бросить исключение в этом случае», это именно то, что мы должны кодировать.Кажется, никто не отвечает на ваш вопрос.
В отличие от того, во что люди любят верить, «избегать недействительных объектов» не часто является практическим решением.
Ответ:
Да, это должно.
Простой пример:
FileStream.Length
в C # /. NET , который выдает,NotSupportedException
если файл не доступен для поиска.источник
У вас все проверяющее имя с ног на голову.
getCompleteName()
не должен знать, какие функции будут его использовать. Таким образом, отсутствиеname
звонкаdisplayCompleteNameOnInvoice()
не являетсяgetCompleteName()
ответственностью.Но,
name
это явная предпосылка дляdisplayCompleteNameOnInvoice()
. Таким образом, он должен взять на себя ответственность за проверку состояния (или он должен делегировать ответственность за проверку другому методу, если вы предпочитаете).источник