Не объявляйте интерфейсы для неизменяемых объектов
[РЕДАКТИРОВАТЬ] Где рассматриваемые объекты представляют объекты передачи данных (DTO) или простые старые данные (POD)
Это разумное руководство?
До сих пор я часто создавал интерфейсы для закрытых классов, которые являются неизменяемыми (данные не могут быть изменены). Я старался быть осторожным, чтобы не использовать интерфейс везде, где мне небезразлична неизменность.
К сожалению, интерфейс начинает проникать в код (и я беспокоюсь не только о моем коде). Вы в конечном итоге получаете интерфейс, а затем хотите передать его некоторому коду, который действительно хочет предположить, что передаваемая ему вещь неизменна.
Из-за этой проблемы я рассматриваю возможность никогда не объявлять интерфейсы для неизменяемых объектов.
Это может иметь последствия в отношении модульного тестирования, но кроме этого, кажется ли это разумным руководством?
Или есть другой шаблон, который я должен использовать, чтобы избежать проблемы "интерфейса распространения", которую я вижу?
(Я использую эти неизменяемые объекты по нескольким причинам: главным образом для обеспечения безопасности потоков, поскольку я пишу много многопоточного кода; но также потому, что это означает, что я могу избежать создания защитных копий объектов, передаваемых в методы. Код становится намного проще в Во многих случаях вы знаете, что что-то является неизменным, а вы - нет, если вы получили интерфейс. На самом деле, часто вы даже не можете сделать защитную копию объекта, на который ссылается интерфейс, если он не обеспечивает клонирование или любой способ его сериализации ...)
[РЕДАКТИРОВАТЬ]
Чтобы предоставить мне гораздо больше смысла в желании сделать объекты неизменяемыми, посмотрите это сообщение в блоге Эрика Липперта:
http://blogs.msdn.com/b/ericlippert/archive/tags/immutability/
Я также должен отметить, что я работаю с некоторыми концепциями более низкого уровня, такими как элементы, которыми манипулируют / передают в многопоточных очередях заданий. По сути, это DTO.
Также Джошуа Блох рекомендует использовать неизменяемые объекты в своей книге «Эффективная Java» .
Следовать за
Спасибо за отзыв, все. Я решил пойти дальше и использовать это руководство для DTO и им подобных. Пока все работает хорошо, но прошла всего неделя ... Тем не менее, выглядит хорошо.
Есть некоторые другие вопросы, связанные с этим, о которых я хочу спросить; особенно то, что я называю «Глубокая или неглубокая неизменность» (номенклатура, которую я украл из Глубокого и неглубокого клонирования) - но это вопрос в другой раз.
источник
List<Number>
который может содержатьInteger
,Float
,Long
,BigDecimal
и т.д. ... Все это неизменны сами.Ответы:
На мой взгляд, ваше правило хорошее (или, по крайней мере, неплохое), но только из-за ситуации, которую вы описываете. Я бы не сказал, что я согласен с этим во всех ситуациях, поэтому, с точки зрения моего внутреннего педанта, я бы сказал, что ваше правило технически слишком широкое.
Обычно вы не определяете неизменяемые объекты, если они по существу не используются в качестве объектов передачи данных (DTO), что означает, что они содержат свойства данных, но очень мало логики и никаких зависимостей. Если это так, то, как кажется, здесь, я бы сказал, что вы можете использовать конкретные типы напрямую, а не интерфейсы.
Я уверен, что будут некоторые пуристы модульного тестирования, которые не согласятся, но, по моему мнению, классы DTO могут быть безопасно исключены из требований модульного тестирования и внедрения зависимостей. Нет необходимости использовать фабрику для создания DTO, так как она не имеет зависимостей. Если все создает DTO напрямую по мере необходимости, то на самом деле нет никакого способа внедрить другой тип, так что нет необходимости в интерфейсе. А поскольку они не содержат логики, нет ничего для модульного тестирования. Даже если они содержат некоторую логику, если у них нет никаких зависимостей, тогда это должно быть тривиально, если необходимо, провести модульное тестирование логики.
Таким образом, я думаю, что принятие правила о том, что все классы DTO не должны реализовывать интерфейс, хотя потенциально и не нужно, не повредит вашему дизайну программного обеспечения. Поскольку у вас есть это требование о том, что данные должны быть неизменными, и вы не можете применить их через интерфейс, я бы сказал, что вполне законно установить это правило в качестве стандарта кодирования.
Однако более серьезной проблемой является необходимость строгого обеспечения чистого уровня DTO. Пока ваши неизменяемые классы без интерфейса существуют только в слое DTO, а ваш слой DTO остается свободным от логики и зависимостей, вы будете в безопасности. Если вы начнете смешивать свои слои, и у вас появятся классы без интерфейса, которые дублируют классы бизнес-уровней, то, я думаю, у вас начнутся большие проблемы.
источник
Раньше я суетился из-за того, что мой код неуязвим для неправильного использования. Я сделал интерфейсы только для чтения для сокрытия мутирующих членов, добавил много ограничений к моим общим сигнатурам и т. Д. И т. Д. Оказалось, что большую часть времени я принимал дизайнерские решения, потому что не доверял своим воображаемым коллегам. «Возможно, когда-нибудь они наймут нового парня начального уровня, и он не узнает, что класс XYZ не может обновить DTO ABC. О, нет!» В другой раз я сосредоточился на неправильной проблеме - игнорируя очевидное решение - не видя леса сквозь деревья.
Я больше никогда не создаю интерфейсы для своих DTO. Я работаю в предположении, что люди, которые касаются моего кода (прежде всего я), знают, что разрешено и что имеет смысл. Если я продолжаю делать ту же самую глупую ошибку, я обычно не пытаюсь укрепить свои интерфейсы. Теперь я провожу большую часть своего времени, пытаясь понять, почему я продолжаю совершать одну и ту же ошибку. Обычно это потому, что я слишком много анализирую или пропускаю ключевую концепцию. С моим кодом стало намного легче работать, так как я перестал быть параноиком. Я также получаю меньше «фреймворков», которые требуют знаний инсайдера для работы в системе.
Мой вывод состоял в том, чтобы найти простейшую вещь, которая работает. Дополнительная сложность создания безопасных интерфейсов просто тратит время на разработку и усложняет простой код. Беспокойтесь о таких вещах, когда ваши библиотеки используют 10 000 разработчиков. Поверьте мне, это избавит вас от большого количества ненужного напряжения.
источник
Это похоже на нормальное руководство, но по странным причинам. У меня было несколько мест, где интерфейс (или абстрактный базовый класс) обеспечивает равномерный доступ к серии неизменяемых объектов. Стратегии имеют тенденцию падать здесь. Государственные объекты имеют тенденцию падать здесь. Я не думаю, что слишком неразумно формировать интерфейс, чтобы он казался неизменным, и документировать его как таковой в вашем API.
Тем не менее, люди склонны перегружать интерфейс Plain Old Data (далее POD) и даже простыми (часто неизменяемыми) структурами. Если ваш код не имеет разумных альтернатив какой-либо фундаментальной структуре, ему не нужен интерфейс. Нет, модульное тестирование не является достаточной причиной для изменения вашего дизайна (доступ к базе данных не является причиной, по которой вы предоставляете интерфейс для этого, это гибкость для будущих изменений) - это не конец света, если ваши тесты использовать эту основную фундаментальную структуру как есть.
источник
Я не думаю, что это проблема, о которой должен беспокоиться разработчик метода доступа. Если
interface X
предполагается, что он является неизменным, то не несет ли разработчик интерфейса ответственность за обеспечение того, чтобы он реализовывал интерфейс неизменным образом?Однако, на мой взгляд, нет такой вещи, как неизменный интерфейс - контракт кода, указанный интерфейсом, применяется только к открытым методам объекта и ничего не относится к внутренним объектам объекта.
Гораздо более распространено видеть неизменность реализованной как декоратор, а не как интерфейс, но выполнимость этого решения действительно зависит от структуры вашего объекта и сложности вашей реализации.
источник
MutableObject
естьn
методы, которые изменяют состояние, иm
методы, которые возвращают состояние,ImmutableDecorator
могут продолжать предоставлять методы, которые возвращают state (m
), и, в зависимости от среды, утверждать или генерировать исключение при вызове одного из изменяемых методов.В C # метод, который ожидает объект «неизменяемого» типа, не должен иметь параметр типа интерфейса, потому что интерфейсы C # не могут указывать контракт неизменности. Таким образом, руководство, которое вы предлагаете, само по себе не имеет смысла в C #, потому что вы не можете сделать это в первую очередь (и будущие версии языков вряд ли позволят вам это сделать).
Ваш вопрос проистекает из тонкого недопонимания статей Эрика Липперта об неизменности. Эрик не определял интерфейсы
IStack<T>
и неIQueue<T>
определял контракты для неизменных стеков и очередей. Они не Он определил их для удобства. Эти интерфейсы позволили ему определять различные типы для пустых стеков и очередей. Мы можем предложить другой дизайн и реализацию неизменяемого стека с использованием одного типа, не требуя интерфейса или отдельного типа для представления пустого стека, но полученный код не будет выглядеть таким чистым и будет немного менее эффективным.Теперь давайте придерживаться дизайна Эрика. Метод, который требует неизменного стека, должен иметь параметр типа,
Stack<T>
а не общий интерфейс,IStack<T>
который представляет абстрактный тип данных стека в общем смысле. Не очевидно, как это сделать при использовании неизменного стека Эрика, и он не обсуждал это в своих статьях, но это возможно. Проблема с типом пустого стека. Вы можете решить эту проблему, убедившись, что вы никогда не получите пустой стек. Это может быть обеспечено путем добавления фиктивного значения в качестве первого значения в стеке и никогда не выталкивать его. Таким образом, вы можете смело приводить результатыPush
иPop
кStack<T>
.Наличие
Stack<T>
инструментовIStack<T>
может быть полезным. Вы можете определить методы, которые требуют стек, любой стек, не обязательно неизменный стек. Эти методы могут иметь параметр типаIStack<T>
. Это позволяет вам передавать ему неизменные стеки. В идеале,IStack<T>
было бы частью самой стандартной библиотеки. В .NET его нетIStack<T>
, но есть и другие стандартные интерфейсы, которые может реализовать неизменяемый стек, что делает тип более полезным.Альтернативный дизайн и реализация неизменяемого стека, о котором я упоминал ранее, использует интерфейс под названием
IImmutableStack<T>
. Конечно, вставка «неизменяемого» в имя интерфейса не делает каждый тип, который его реализует, неизменным. Однако в этом интерфейсе договор неизменности является просто устным. Хороший разработчик должен уважать это.Если вы разрабатываете небольшую внутреннюю библиотеку, вы можете договориться со всеми в команде о соблюдении этого контракта и можете использовать ее
IImmutableStack<T>
в качестве типа параметров. В противном случае вы не должны использовать тип интерфейса.Я хотел бы добавить, поскольку вы пометили вопрос C #, что в спецификации C # нет такой вещи, как DTO и POD. Поэтому их отбрасывание или точное определение улучшает вопрос.
источник