Я вижу себя использующим все больше и больше неизменяемых типов, когда не ожидается, что экземпляры класса будут изменены . Это требует больше работы (см. Пример ниже), но упрощает использование типов в многопоточной среде.
В то же время я редко вижу неизменяемые типы в других приложениях, даже если изменчивость никому не пойдет на пользу.
Вопрос: почему неизменяемые типы так редко используются в других приложениях?
- Это потому, что писать код для неизменяемого типа длиннее,
- Или я что-то упустил и есть некоторые важные недостатки при использовании неизменяемых типов?
Пример из реальной жизни
Допустим, вы получаете Weather
от RESTful API вот так:
public Weather FindWeather(string city)
{
// TODO: Load the JSON response from the RESTful API and translate it into an instance
// of the Weather class.
}
Как правило, мы видим (новые строки и комментарии удалены для сокращения кода):
public sealed class Weather
{
public City CorrespondingCity { get; set; }
public SkyState Sky { get; set; } // Example: SkyState.Clouds, SkyState.HeavySnow, etc.
public int PrecipitationRisk { get; set; }
public int Temperature { get; set; }
}
С другой стороны, я бы написал это так, учитывая, что получать Weather
от API, а затем модифицировать его было бы странно: менять Temperature
или Sky
не менять погоду в реальном мире, и изменение CorrespondingCity
не имеет смысла.
public sealed class Weather
{
private readonly City correspondingCity;
private readonly SkyState sky;
private readonly int precipitationRisk;
private readonly int temperature;
public Weather(City correspondingCity, SkyState sky, int precipitationRisk,
int temperature)
{
this.correspondingCity = correspondingCity;
this.sky = sky;
this.precipitationRisk = precipitationRisk;
this.temperature = temperature;
}
public City CorrespondingCity { get { return this.correspondingCity; } }
public SkyState Sky { get { return this.sky; } }
public int PrecipitationRisk { get { return this.precipitationRisk; } }
public int Temperature { get { return this.temperature; } }
}
источник
{get; private set;}
, и даже изменяемый должен иметь конструктор, потому что все эти поля должны быть всегда установлены, и почему бы вам не применять это? Внесение этих двух совершенно разумных изменений приводит к их характеристике и паритету LoC.Ответы:
Я программирую на C # и Objective-C. Мне действительно нравится неизменяемая типизация, но в реальной жизни мне всегда приходилось ограничивать ее использование, главным образом для типов данных, по следующим причинам:
StringBuilder
илиUriBuilder
в C #, илиWeatherBuilder
в вашем случае. Это главная причина для меня, так как многие классы, которые я проектирую, не стоят таких усилий.Короче говоря, неизменность хороша для объектов, которые ведут себя как значения или имеют только несколько свойств. Прежде чем сделать что-либо неизменным, вы должны подумать о необходимых усилиях и удобстве использования самого класса после того, как сделаете его неизменным.
источник
Просто обычно неизменяемые типы, созданные в языках, которые не вращаются вокруг неизменности, будут иметь тенденцию тратить больше времени на разработку, а также потенциально использовать, если им потребуется какой-то объект типа «конструктор» для выражения желаемых изменений (это не означает, что в целом работы будет больше, но в этих случаях есть предоплата). Кроме того, независимо от того, позволяет ли язык действительно легко создавать неизменяемые типы или нет, он будет всегда требовать некоторой обработки и дополнительных затрат памяти для нетривиальных типов данных.
Создание функций, лишенных побочных эффектов
Если вы работаете на языках, которые не вращаются вокруг неизменности, то я думаю, что прагматический подход заключается не в стремлении сделать каждый тип данных неизменным. Потенциально гораздо более продуктивный образ мышления, который дает вам много таких же преимуществ, заключается в том, чтобы сосредоточиться на максимизации количества функций в вашей системе, которые вызывают нулевые побочные эффекты .
В качестве простого примера, если у вас есть функция, которая вызывает побочный эффект, подобный этому:
Тогда нам не нужен неизменяемый целочисленный тип данных, который запрещает операторам, таким как назначение после инициализации, чтобы эта функция избегала побочных эффектов. Мы можем просто сделать это:
Теперь функция не связывается с
x
чем-либо и выходит за ее пределы, и в этом тривиальном случае мы могли бы даже сократить некоторые циклы, избегая любых издержек, связанных с косвенным обращением / псевдонимами. По крайней мере, вторая версия не должна быть дороже в вычислительном отношении, чем первая.Вещи, которые дорого копировать в полном объеме
Конечно, в большинстве случаев это не так тривиально, если мы хотим, чтобы функция не вызывала побочных эффектов. Сложный реальный вариант использования может быть больше похож на это:
В этот момент сетке может потребоваться пара сотен мегабайт памяти с более чем сотней тысяч полигонов, еще больше вершин и ребер, множественные карты текстур, морфинговые цели и т. Д. Было бы действительно дорого скопировать всю эту сетку, просто чтобы сделать это
transform
функция без побочных эффектов, например так:И именно в этих случаях копирование чего-либо во всей полноте, как правило, было бы эпической затратами, когда я нашел полезным превратить
Mesh
в постоянную структуру данных и неизменный тип с аналогичным «строителем» для создания модифицированных версий этого, чтобы может просто поверхностное копирование и подсчет ссылок, которые не являются уникальными. Все дело в том, чтобы писать функции меша без побочных эффектов.Постоянные структуры данных
И в тех случаях, когда копирование всего настолько невероятно дорого, я обнаружил, что попытка создать неизменяемый объект
Mesh
действительно окупится, даже если он стоил немного дороже, потому что он не просто упростил безопасность потоков. Это также упростило неразрушающее редактирование (позволяя пользователю наслоить операции с сеткой без изменения его оригинальной копии), отменить системы (теперь система отмены может просто сохранять неизменную копию сетки до изменений, сделанных операцией, не разрушая память use) и безопасность исключений (теперь, если исключение возникает в вышеупомянутой функции, функция не должна откатываться и отменять все свои побочные эффекты, поскольку она не вызывала их с самого начала).В этих случаях я могу с уверенностью сказать, что время, необходимое для того, чтобы сделать эти здоровенные структуры данных неизменяемыми, сэкономило больше времени, чем стоило, так как я сравнил затраты на обслуживание этих новых конструкций с прежними, которые вращались вокруг изменчивости и функций, вызывающих побочные эффекты, и прежние изменяемые конструкции стоили гораздо больше времени и были гораздо более подвержены человеческим ошибкам, особенно в тех областях, где разработчикам действительно хочется пренебречь во время кризиса, например, безопасность исключений.
Поэтому я считаю, что неизменяемые типы данных действительно оправдывают себя в этих случаях, но не все должно быть сделано неизменным, чтобы большинство функций в вашей системе было лишено побочных эффектов. Многие вещи достаточно дешевы, чтобы просто скопировать их полностью. Также многие приложения реального мира должны вызывать некоторые побочные эффекты здесь и там (по крайней мере, как сохранение файла), но обычно существует гораздо больше функций, которые могут быть лишены побочных эффектов.
Смысл в том, чтобы иметь несколько неизменных типов данных для меня, чтобы убедиться, что мы можем написать максимальное количество функций, чтобы избежать побочных эффектов, не неся эпических издержек в виде глубокого копирования массивных структур данных влево и вправо полностью, когда только небольшими порциями из них должны быть изменены. Наличие постоянных структур данных в этих случаях в конечном итоге превращается в деталь оптимизации, позволяющую нам писать наши функции без побочных эффектов, не затрачивая при этом огромных затрат.
Неизменные накладные расходы
Теперь концептуально изменяемые версии всегда будут иметь преимущество в эффективности. Всегда есть вычислительные затраты, связанные с неизменяемыми структурами данных. Но я нашел это достойным обменом в случаях, которые я описал выше, и вы можете сосредоточиться на том, чтобы сделать накладные расходы достаточно минимальными по своей природе. Я предпочитаю такой подход, когда правильность становится проще, а оптимизация становится сложнее, чем оптимизация легче, а правильность становится сложнее. Это не так уж и деморализующе - иметь код, который функционирует совершенно корректно и нуждается в дополнительных настройках кода, который в первую очередь работает некорректно, независимо от того, насколько быстро он достигает своих неверных результатов.
источник
Единственный недостаток, который я могу вспомнить, заключается в том, что теоретически использование неизменяемых данных может быть медленнее, чем изменяемых: создание нового экземпляра и сбор предыдущего происходит медленнее, чем изменение существующего.
Другая «проблема» заключается в том, что вы не можете использовать только неизменяемые типы. В конце вы должны описать состояние и использовать изменяемые типы для этого - без изменения состояния вы не сможете выполнять какую-либо работу.
Но все же общее правило заключается в том, чтобы использовать неизменяемые типы везде, где это возможно, и делать типы изменяемыми только тогда, когда для этого действительно есть причина ...
И чтобы ответить на вопрос « Почему неизменяемые типы так редко используются в других приложениях? » - я действительно не думаю, что они… куда бы вы ни посмотрели, все рекомендуют сделать ваши классы настолько неизменными, насколько это возможно… например: http://www.javapractices.com/topic/TopicAction.do?Id=29
источник
Car
объект постоянно обновляется с указанием местоположения конкретного физического автомобиля, то, если у меня есть ссылка на этот объект, я могу быстро и легко узнать местонахождение этого автомобиля. Если бы ониCar
были неизменными, найти текущее местонахождение было бы гораздо сложнее.Чтобы смоделировать любую реальную систему, в которой вещи могут меняться, изменяемое состояние нужно будет где-то каким-то образом кодировать. Существует три основных способа, которыми объект может содержать изменяемое состояние:
Использование first облегчает объекту создание неизменного снимка текущего состояния. Использование второго облегчает объекту создание живого представления текущего состояния. Использование третьего может иногда сделать некоторые действия более эффективными в тех случаях, когда нет ожидаемой потребности в неизменяемых снимках или живых представлениях.
Помимо того факта, что обновление состояния, хранящегося с использованием изменяемой ссылки на неизменяемый объект, часто медленнее, чем обновление состояния, хранящегося с использованием изменяемого объекта, использование изменяемой ссылки потребует отказа от создания дешевого живого представления состояния. Если вам не нужно создавать живое изображение, это не проблема; если, однако, нужно будет создать живое представление, невозможность использовать неизменную ссылку сделает все операции с представлением - как чтение, так и запись- намного медленнее, чем они были бы. Если потребность в неизменяемых снимках превышает потребность в динамических представлениях, то улучшенная производительность неизменяемых снимков может оправдать снижение производительности для динамических представлений, но если нужны динамические представления и не нужны моментальные снимки, то использование неизменяемых ссылок на изменяемые объекты является способом идти.
источник
В вашем случае ответ в основном потому, что C # плохо поддерживает неизменяемость ...
Было бы здорово, если бы:
все будет неизменным по умолчанию, если не указано иное (например, с ключевым словом 'mutable'), смешивание неизменяемых и изменяемых типов сбивает с толку
методы мутации (
With
) будут автоматически доступны - хотя это уже может быть достигнуто, см. сбудет способ сказать, что результат определенного вызова метода (то есть
ImmutableList<T>.Add
) не может быть отброшен или, по крайней мере, выдаст предупреждениеИ главным образом, если компилятор мог максимально обеспечить неизменность там, где это требуется (см. Https://github.com/dotnet/roslyn/issues/159 )
источник
MustUseReturnValueAttribute
Что касается третьего пункта, у ReSharper есть собственный атрибут, который делает именно это.PureAttribute
имеет тот же эффект и даже лучше для этого.Невежество? Неопытность?
Неизменные объекты сегодня широко рассматриваются как превосходные, но это относительно недавняя разработка. Инженеры, которые не идут в ногу со временем или просто застряли в «том, что они знают», не будут их использовать. И для их эффективного использования требуются небольшие изменения в дизайне. Если приложения старые или у инженеров слабые навыки проектирования, их использование может быть неудобным или иным образом хлопотным.
источник