Обязательные атрибуты в Smalltalk
Я пишу классы на Pharo Smalltalk, но полагаю, что этот вопрос подходит для других реализаций Smalltalk.
Я знаю, что способ принудительного использования экземпляров с определенными атрибутами - это предоставить метод класса для создания экземпляра, а затем предложить использовать метод создания класса. Но любой пользователь знает, что new или basicNew можно использовать в любое время.
Я думал об аннулировании new и basicNew, вызывая исключение, но это кажется слишком радикальной мерой, учитывая, что иногда мне может потребоваться создать экземпляры для отладки, например.
Есть ли другая библиотека или механизм для обеспечения выполнения этих конкретных атрибутов?
2 ответа
Нет. И это хорошо.
Вот несколько подходов, которые вы можете использовать:
Убедитесь, что ваши объекты действительны, предоставив проверки для них. Это очень широкая тема, поэтому я просто скажу, что структура проверки - это возможность проверить ваши объекты и как-то сделать явными любые правила, которые они не соблюдают.
В зависимости от того, что вы делаете, вы можете ограничить проверки графическим интерфейсом, когда объекты рождаются или изменяются. Однако более сложный подход должен позволять любому объекту решать, является ли он действительным или нет (в данном контексте).
Исключить отдельные сеттеры из протокола на стороне экземпляра. Предоставьте только несколько наборов ключевых слов, которые потерпят неудачу, если что-то отсутствует или не подходит. Это означает, что вы бы предоставили такие методы, как
Point >> #x:y:
но нетPoit >> #x:
или жеPoint >> #y:
,Как показывает пример, вам будет трудно освоить эту практику в полной мере, поскольку базовые классы не следуют этому стилю. Также обратите внимание, что эта практика потребует некоторой проверки, потому что проверка только для
notNil
обычно слишком наивен.Расслабьтесь и ничего не делайте, пока не возникнет необходимость в решении подобных проблем.
Другими словами, откладывайте проблему до тех пор, пока ваше программное обеспечение не будет достаточно развито, чтобы привлечь ваше внимание к тому, как создаются и изменяются его объекты.
Причина, по которой я утверждаю, что вещи хороши в том виде, в каком они есть, заключается в том, что Smalltalk, традиционно отдавая предпочтение открытости, а не безопасности, облегчал и даже поощрял людей экспериментировать, даже "неправильным" образом. Каждая функция, направленная на предотвращение поломки объектов, рано или поздно остановит вас в ремонте. Что еще более важно, они будут потреблять много энергии, которая в противном случае могла бы быть использована для более продуктивных целей.
Я согласен с ответом Леандро. Оборонительное программирование редко используется в Smalltalk. Нет никакой статической типизации для принудительного применения чего-либо, ни личных сообщений. Smalltalk открыт и имеет позднюю привязку. Но, с другой стороны, ошибки редко бывают катастрофическими (Smalltalk обычно не вылетает при ошибке).
Эта поздняя привязка наряду с постепенным восстановлением после ошибок делает эволюцию системы очень легким процессом. Когда вы программируете на Smalltalk, вы ничего не делаете, кроме как заставляете живую систему развиваться, если вы думаете об этом.
Поскольку единственное, что у нас есть, - это отправка сообщений, мы можем в конечном итоге использовать это для переговоров по контрактам или просто для проверки некоторых предварительных условий.
Стратегия, которую вы предлагаете, возможна через использование исключения. Идея заключается в том, что иногда лучше иметь раннее исключение, максимально близкое к основной причине, чем позднее, которое сложнее отладить и исправить. Смотрите, например, сообщение #shouldNotImplement
и его отправителей: вы увидите, что иногда он используется для предотвращения использования #new
,
Также не забывайте, что есть #initialize
сообщение, которое можно использовать для задания разумного значения по умолчанию для переменных экземпляра (немного похоже на конструктор по умолчанию в C++). Есть варианты, когда вы хотите передать дополнительную информацию: для коллекций, которые обычно создаются через #new:
E сть #initialize:
сообщение, принимающее размер в качестве аргумента. Вы можете улучшить подобные механизмы по желанию для передачи другой информации при создании.
Но не пытайтесь предотвратить использование #basicNew
, Вы бы причиняли себе боль, ломая некоторые службы, полагаясь на эту низкоуровневую функцию, например, изменяя существующие экземпляры при изменении макета вашего класса, например копирование, хранение во внешних файлах и т. Д., См. #adoptInstance:
например.
Вместо этого вы должны научиться доверять пользователям вашей библиотеки (включая себя): пользователи не будут использовать basicNew
если они не знают, что они делают (они узнают это рано или поздно). И, как сказал Амос, документируйте контракты и ожидания в комментариях класса или сообщения или модульных тестах, чтобы люди могли учиться быстрее и, возможно, использовали не столь привлекательные имена, как basicNew, когда вы хотите выразить, что сообщение предназначено только для осведомленного использования.
EDIT, кстати, если вы запретите basicNew, вы вообще не сможете создавать экземпляры, поэтому вам придется создать новое сообщение, вызывающее примитив для создания экземпляра, и в конце вы просто получите запутанный / сложный код ни за что, потому что вы просто переместили проблему. Если вы не делаете очень неприятные вещи, такие как:
basicNew
thisContext sender method selector == #mySepcialCreationMessageWithParameter: ifTrue: [^super basicNew].
^self error: 'new instances should be created with mySepcialCreationMessageWithParameter:'
Как я уже говорил выше, вы можете играть с этим, но не делайте этого по-настоящему.
РЕДАКТИРОВАТЬ 2 Другая точка зрения заключается в том, что кодирование является социальной деятельностью, а код, который вы пишете в Smalltalk, предназначен не только для программирования автомата. Это для чтения и повторного использования людьми. В этом контексте защитное программирование похоже на управление с помощью принуждения. Вместо того, чтобы тратить время на попытки ограничения, более эффективно тратить время на создание простых и логических абстракций, которые можно легко понять и использовать повторно, возможно, в других контекстах, которые вы не предусмотрели.