У меня возникла проблема дизайна, касающаяся свойств .NET.
interface IX
{
Guid Id { get; }
bool IsInvalidated { get; }
void Invalidate();
}
Проблема:
Этот интерфейс имеет два свойства только для чтения Id
и IsInvalidated
. Однако тот факт, что они доступны только для чтения, сам по себе не является гарантией того, что их значения останутся постоянными.
Скажем так, это было мое намерение прояснить, что ...
Id
представляет собой постоянное значение (которое, следовательно, может быть безопасно кэшировано), в то время какIsInvalidated
может изменить его значение в течение времени жизниIX
объекта (и поэтому не должно кэшироваться).
Как я могу изменить, interface IX
чтобы сделать этот контракт достаточно четким?
Мои собственные три попытки решения:
Интерфейс уже хорошо продуман. Наличие вызываемого метода
Invalidate()
позволяет программисту сделать вывод, чтоIsInvalidated
оно может повлиять на значение свойства с таким же именем .Этот аргумент верен только в тех случаях, когда метод и свойство имеют одинаковые имена.
Дополните этот интерфейс событием
IsInvalidatedChanged
:bool IsInvalidated { get; } event EventHandler IsInvalidatedChanged;
Наличие
…Changed
события дляIsInvalidated
состоянияId
означает, что это свойство может изменить его значение, а отсутствие аналогичного события для - это обещание, что это свойство не изменит свое значение.Мне нравится это решение, но это много дополнительных вещей, которые могут вообще не привыкнуть.
Замените свойство
IsInvalidated
методомIsInvalidated()
:bool IsInvalidated();
Это может быть слишком тонким изменением. Предполагается, что это намек на то, что значение вычисляется заново каждый раз, что не было бы необходимо, если бы оно было константой. В теме MSDN «Выбор между свойствами и методами» говорится следующее:
Используйте метод, а не свойство, в следующих ситуациях. […] Операция возвращает новый результат при каждом вызове, даже если параметры не меняются.
Какие ответы я ожидаю?
Меня больше всего интересуют совершенно разные решения проблемы, а также объяснение того, как они побеждают мои вышеупомянутые попытки.
Если мои попытки логически ошибочны или имеют существенные недостатки, о которых еще не говорилось, например, что остается только одно решение (или ни одного), я хотел бы услышать о том, где я ошибся.
Если недостатки незначительны и после принятия во внимание остается более одного решения, оставьте комментарий.
По крайней мере, я хотел бы получить отзыв о том, какое решение вы предпочитаете и по какой причине.
источник
IsInvalidated
нуженprivate set
?class Foo : IFoo { private bool isInvalidated; public bool IsInvalidated { get { return isInvalidated; } } public void Invalidate() { isInvalidated = true; } }
InvalidStateException
s, например, но я не уверен, возможно ли это даже теоретически. Хотя было бы неплохо.Ответы:
Я бы предпочел решение 3 над 1 и 2.
Моя проблема с решением 1 : что происходит, если нет
Invalidate
метода? Предположим интерфейс соIsValid
свойством, которое возвращаетDateTime.Now > MyExpirationDate
; вам может не понадобиться явныйSetInvalid
метод здесь. Что если метод влияет на несколько свойств? Предположим, что тип соединения сIsOpen
иIsConnected
свойства - оба зависят отClose
метода.Решение 2. Если единственная цель события - сообщить разработчикам, что свойство с одинаковым именем может возвращать разные значения при каждом вызове, то я настоятельно рекомендую это сделать. Вы должны держать свои интерфейсы короткими и ясными; Более того, не всякая реализация может вызвать это событие для вас. Я снова воспользуюсь своим
IsValid
примером сверху: вам нужно будет реализовать Timer и запустить событие, когда вы достигнетеMyExpirationDate
. В конце концов, если это событие является частью вашего открытого интерфейса, то пользователи интерфейса будут ожидать, что это событие будет работать.Это сказанное об этих решениях: они не плохи. Наличие метода или события БУДЕТ указать, что свойство с одинаковым именем может возвращать разные значения при каждом вызове. Все, что я говорю, это то, что одних их недостаточно, чтобы всегда передать это значение.
Решение 3 - это то, к чему я стремлюсь. Как уже говорил Авив, это может работать только для разработчиков на C #. Для меня, как для C #, тот факт, что
IsInvalidated
это не свойство, немедленно передает значение «это не просто средство доступа, здесь что-то происходит». Это не относится ко всем, и, как указала MainMa, сама платформа .NET здесь не согласована.Если вы хотите использовать решение 3, я бы порекомендовал объявить его конвенцией, и вся команда должна следовать ему. Документация тоже важна, я считаю. На мой взгляд , на самом деле это довольно просто намек на изменение значения: « возвращает истину , если значение по- прежнему в силе », « указывает , является ли соединение уже было открыто » против « возвращает истину , если этот объект является действительным ». Это не повредит, хотя быть более явным в документации.
Так что мой совет будет:
Объявите решение 3 соглашением и последовательно следуйте ему. В документации свойств укажите, имеет ли свойство изменяющиеся значения. Разработчики, работающие с простыми свойствами, будут правильно предполагать, что они не изменяются (т.е. изменяются только при изменении состояния объекта). Разработчики сталкивающиеся метод , который звучит , как это может быть свойством (
Count()
,Length()
,IsOpen()
) знают , что коснуться происходит и (надеюсь) читать метод документы , чтобы понять , что именно метод делает и как он ведет себя.источник
Существует четвертое решение: полагаться на документацию .
Откуда ты знаешь, что
string
класс неизменен? Вы просто знаете это, потому что вы прочитали документацию MSDN.StringBuilder
с другой стороны, изменчив, потому что, опять же, документация говорит вам об этом.Ваше третье решение неплохое, но .NET Framework его не придерживается . Например:
являются свойствами, но не ожидайте, что они останутся постоянными каждый раз.
То, что последовательно используется в самой .NET Framework, так это:
это метод, а:
это собственность. В первом случае выполнение вещи может потребовать дополнительной работы, включая, например, запрос к базе данных. Эта дополнительная работа может занять некоторое время. В случае свойства ожидается, что оно займет короткое время: действительно, длина может измениться за время существования списка, но для его возврата практически ничего не требуется.
С классами просто взгляд на код ясно показывает, что значение свойства не изменится в течение времени жизни объекта. Например,
Понятно: цена останется прежней.
К сожалению, вы не можете применить один и тот же шаблон к интерфейсу, и, учитывая, что C # не достаточно выразителен в таком случае, следует использовать документацию (включая XML-документацию и диаграммы UML), чтобы закрыть пробел.
источник
Lazy<T>.Value
для вычисления может потребоваться очень много времени (при первом обращении к нему).Мои 2 цента:
Вариант 3: Как не-C # -er, это не будет большой подсказкой; Однако, судя по приведенной вами цитате, опытным программистам на C # должно быть ясно, что это что-то значит. Вы все еще должны добавить документы об этом, хотя.
Вариант 2: Если вы не планируете добавлять события в каждую вещь, которая может измениться, не ходите туда.
Другие опции:
id
, которое обычно считается неизменным даже в изменяемых объектах.источник
Другой вариант - разделить интерфейс: при условии, что после вызова
Invalidate()
каждый вызовIsInvalidated
должен возвращать одно и то же значениеtrue
, кажется, нет никаких причин для вызоваIsInvalidated
той же части, которая была вызванаInvalidate()
.Итак, я предлагаю, вы либо проверяете на недействительность, либо вызываете ее, но кажется неразумным делать и то, и другое. Поэтому имеет смысл предложить один интерфейс, содержащий
Invalidate()
операцию, для первой части и другой интерфейс для проверки (IsInvalidated
) другой. Поскольку из сигнатуры первого интерфейса довольно очевидно, что это приведет к тому, что экземпляр станет недействительным, остающийся вопрос (для второго интерфейса) действительно довольно общий: как указать, является ли данный тип неизменным .Я знаю, что это не дает прямого ответа на вопрос, но, по крайней мере, сводит его к вопросу о том, как пометить некоторый тип неизменяемым , что является довольно распространенной и понятной проблемой. Например, предполагая, что все типы являются изменяемыми, если не определено иначе, вы можете заключить, что второй интерфейс (
IsInvalidated
) является изменяемым и, следовательно, может времяIsInvalidated
от времени изменять значение . С другой стороны, предположим, что первый интерфейс выглядел следующим образом (псевдосинтаксис):Поскольку он помечен как неизменяемый, вы знаете, что идентификатор не изменится. Конечно, вызов invalidate приведет к изменению состояния экземпляра, но это изменение не будет заметно через этот интерфейс.
источник
[Immutable]
если он охватывает методы, единственная цель которых - вызвать мутацию. Я бы переместилInvalidate()
метод вInvalidatable
интерфейс.Id
, вы будете получать одно и то же значение каждый раз. То же самое относится кInvalidate()
(вы ничего не получаете, так как его тип возвращаемого значенияvoid
). Следовательно, тип является неизменным. Конечно, у вас будут побочные эффекты , но вы не сможете наблюдать их через интерфейсIX
, поэтому они не окажут никакого влияния на клиентовIX
.IX
является неизменным. И что они являются побочными эффектами; они просто распределяются между двумя разделенными интерфейсами, так что клиентам не нужно заботиться (в случаеIX
) или что очевидно, каким может быть побочный эффект (в случаеInvalidatable
). Но почему бы не перейтиInvalidate
кInvalidatable
? Это сделало быIX
неизменным и чистым, иInvalidatable
не стало бы более изменчивым или более нечистым (потому что это уже оба из них).Invalidate()
и одинIsInvalidated
и тот же потребитель интерфейса . Если вы звонитеInvalidate()
, вы должны знать, что он становится недействительным, так зачем это проверять? Таким образом, вы можете предоставить два разных интерфейса для разных целей.