Абасс... обсудите рахитекурту
Драма в действиях - с прологом, эпилогом и интермедиями. Задача выделена жирным.
Всё просто - в классе есть свойства CurrentValue и MaxValue - они отвечают соответственно за два значения одного параметра. Сначала для простоты я написал так
public double CurrentValue { get; set; } public double MaxValue { get; set; }
Теперь добавим условие для CurrentValue - оно не должно быть больше MaxValue, но не меньше 0. Для этого добавлю валидацию для CurrentValue, расписав его геттер и сеттер:
double currentValue; public double CurrentValue { get => currentValue; // если value вне диапазона, то вернуть ту или другую границу диапазона - смотря в какую сторону выходит set => currentValue = Math.Clamp(value, 0, MaxValue); }
Далее усложняем. Хоть MaxValue и называется "максимальным", но оно тоже может изменяться, только менее часто, чем CurrentValue. Т.е. каждый раз, когда меняется MaxValue, CurrentValue должно быть пересчитано. Пришлось отказаться от автосвойств и расписать геттер и сеттер и для MaxValue:
double maxValue; public double MaxValue { get => maxValue; set { maxValue = value; CurrentValue = CurrentValue; // чтобы принудительно вызвать валидацию CurrentValue } }
Т.к. maxValue уже установлено, то при валидации CurrentValue внутри сеттера MaxValue в этом месте, геттер MaxValue будет возвращать уже новое значение maxValue - т.е. тут всё законно.
Далее ещё усложняем. У меня CurrentValue и MaxValue на самом деле находятся в двух разных классах - один потомок другого. Один класс имеет лишь просто значения CurrentValue, а другой - добавляет MaxValue. Это потому, что есть параметры, которые меняются часто между минимумом и максимумом, а есть, которые просто меняются, и при этом нечасто. Где просто меняются, достаточно одного значения параметра - я назвал его не текущим, а максимальным (MaxValue ), а где часто - там нужно ещё и текущий - CurrentValue. Т.е. пока я просто разнёс их в разные классы:
public class Param { double maxValue; public double MaxValue { get => maxValue; set { maxValue = value; CurrentValue = CurrentValue; } } } public class ParamVolatile : Param { double currentValue; public double CurrentValue { get => currentValue; set => currentValue = Math.Clamp(value, 0, MaxValue); } }
Тут загвоздка - класс Param не знает о CurrentValue из ParamVolatile. На помощь пришло переопределение свойства - мы оставим MaxValue в классе Param девственно чистым, не знающим о существовании всяких ветренных и непостоянных ParamVolatile, а ParamVolatile будет иметь своё собственное "видение" для MaxValue - с блекджеком и так далее:
public class Param { double maxValue; public virtual double MaxValue { get => maxValue; set => maxValue = value; } } public class ParamVolatile : Param { double maxValue; public override double MaxValue { get => maxValue; set { maxValue = value; CurrentValue = CurrentValue; } } double currentValue; public double CurrentValue { get => currentValue; set => currentValue = Math.Clamp(value, 0, MaxValue); } }
Но зачем нам тогда наследование, если у нас ParamVolatile имеет своё собственное своство MaxValue? А это для заморочек с приведениями типов. Надо, короче. Но всё же мы можем кое-что упростить. Например, зачем нам два приватных поля maxValue в двух разных классах? Это же по сути два разных значения, никак не связанных. А нам по идее нужно лишь одно. Давайте уберём это поле из класса ParamVolatile и заставим использовать этот класс поле из Param - через свойство MaxValue класса Param. Код изменённого класса ParamVolatile - поменялось лишь свойство MaxValue и мы убрали дублирующее поле maxValue
public class ParamVolatile : Param { public override double MaxValue { get => base.MaxValue; set { base.MaxValue = value; CurrentValue = CurrentValue; } } double currentValue; public double CurrentValue { get => currentValue; set => currentValue = Math.Clamp(value, 0, MaxValue); } }
Ну и добавим валидацию и для MaxValue базового класса - а то чё это оно у нас без валидации, зря чтоли от автосвойства отказывался. Просто обозначу валидирующую функцию и приведу окончательный код обоих классов:
public class Param { double maxValue; public virtual double MaxValue { get => maxValue; set => maxValue = ValidateMaxValue(value); } } public class ParamVolatile : Param { public override double MaxValue { get => base.MaxValue; set { base.MaxValue = value; CurrentValue = CurrentValue; } } double currentValue; public double CurrentValue { get => currentValue; set => currentValue = Math.Clamp(value, 0, MaxValue); } }
Теперь у нас по идее есть валидация в базовом классе для свойства MaxValue, которая та же самая используется и в производном классе для этого же переопределённого свойства, и плюс специальная механика для свойства CurrentValue в производном классе, завязанная на изменение свойства MaxValue базового класса. Потестил - вроде работает.
Можно ли сделать лучше для описанной задачи (напомню - выделено жирным)? Может что с интерфейсами намутить? Кода не так уж много, и вроде вылизан, а интерфейсы лишь добавят лапшы, нет? Меня здесь смущает в основном запись CurrentValue = CurrentValue для принудительной валидации CurrentValue. В комментах к коду я написал, почему так делаю, но вроде можно вместо такой записи (присваивание самому себе) и повторить код сеттера CurrentValue:
currentValue = Math.Clamp(value, 0, MaxValue);
Но это же повтор кода! Страшное нарушение какого-то там принципа каких-то там шибко умных старпёров. Короче, табу. Поэтому решил, что просто присвоить свойство самому себе выйдет дешевле. Комментиком сверху пригладил, зачем так сделал, и зашибись.
Сразу говорю, что не юзал разные валидирующие атрибуты типа RangeAttribute, т.к. они ничего не валидируют, а лишь задают валидирующие значения и сообщения об ошибках - для них надо писать свою функцию валидации, которая вытаскивает из этих атрибутов их значения (через рефлексию, ага) и производит собственно валидацию. Для некоторых фреймворков типа ASP.NET MVC это уже написано и работает под капотом, но для своего кода надо писать самому. Зачем так сложно, если эти значения у меня уже в готовых свойствах, как и валидация.
Кстати, пошарился по МСДНу и Стековерфлоу - разбираются всякие переопределения, сокрытия свойств, но нигде нет примера переопределениям свойства с его полем - нужно ли поле тоже "переопределять". Да, поля нельзя переопределять, но как их использовать в переопределённом свойстве? Все примеры переопределения - лишь для автосвойств. Например. Или здесь темку как-то заболтали, т.к. автор был неконкретен и сразу скатился в "или... или... или...". Или все статьи про полиморфизм, про переопределение, про использование переопределения или сокрытия - там везде разбираются лишь простейшие примеры, типа возврата константы или переопределения свойства вообще без поля и кода, или лишь примеры с методами, а не свойствами. И ни одного смешанного примера, где бы возвращалось поле поддержки (backing field). То, что для свойств с полем в базовом классе, чтобы использовать это поле в классе-потомке, нужно вызвать базовую версию геттера или сеттера, которая работает с базовым полем (а не заводить второе поле для класса-потомка) - нигде не показывается. А я вот в своём примере выше это разобрал. А вот если хочешь скрыть свойство, тогда да - лучше завести второе поле для класса-потомка.
Но это так, дополнение к моему первому посту. Попробуйте какого-нибудь сеньёра поспрашивать про не очень стандартный пример не из букваря - посыпится наверняка. ))
Если привык к самым стандартным конструкциям и годами только их и встречал, а потом встретил чуть более не такое - будет сидеть и репу чесать, хотя "10+ лет опыта, ценный специалист и заслуженный сеньёр". ))
ИЧСХ, вроде спрашиваешь поисковик конкретный и довольно короткий вопрос, типа
"C# override property with the same backing field"
или
"C# override property example"
И тебе выдаёт либо всякую муть, либо простейшие примеры переопределения без использования поля поддержки базового класса. Гугл, Майкрософт, мать вашу, вы своих долбаных ИИ чему обучаете, если они ни ответа дать не могут (что их поисковики, что их ИИ чат боты), ни маршрут нормальный в навигаторе построить - самые насущные вопросы. Зато всякую херню делать годами и десятилениями, инклюзивность и прочая смузёво-скриптизёрская параша - это хлебом не корми.
как выглядит функция ValidateMaxValue?
В общем виде так
double ValidateMaxValue(double incomingValue)
А в реальности сложнее - у класса Param есть ещё свойство ParamType, которое тоже передаётся в функцию валидации, и в зависимости от него валидируется. Но это детали моей реализации для моего случая. В общем же виде я выше написал.
Последний пример наверное будет работать, пока у класса Param только один наследник.
Если ты присвоишь новое значение MaxValue во втором наследнике, то первый же об этом ничего не узнает?
А зачем двум разных наследникам - т.е. двум разным параллельным классам - знать, что происходит в параллельном классе? Это же будут разные объекты, у которых из общего лишь базовый класс. Или какой пример наследования вы имели ввиду - уже не от Param, а от ParamVolatile?
Пока у меня действительно лишь один наследник от Param, и всё вроде работает.
По-моему, у меня как-то запутана валидация - она раздроблена между свойствами: вот эта запись это по сути тоже валидация
set => currentValue = Math.Clamp(value, 0, MaxValue);
А у меня видно же, что свойства связанные - одно от другого зависит. По идее, связанные свойства должны валидироваться одной функцией валидации, которая учитывает значение сразу всех связанных свойств. Логично? Этот описанный мной вариант масштабируется на сколько угодно свойств - просто вызываешь одну функцию валидации на любое изменение любого свойства из группы зависимых. А вариант в моём первом посте масштабировать трудно - нужно переписать много кода и не ошибиться в заимных зависимостях.
ну, для начала слишком много букафф .
И опять что то из мира извращений.
У нас есть логика которая требует одновременного знания/изменения 2 значений. Сначала разносим это по разным углам, а после пытаемся как то косо-криво связать.
Отчего не сделать всё требуемую логику в одном объекте, а после пользовать его где надо?
Это я ещё до фабрики фабрик не добрался - так, на уровне букваря ковыряю. )))
Просто писал, как придумывал такую архитектуру, по шагам.
Что значит "вся логика в одном объекте"? Есть базовый класс, которого достаточно для многих объектов. И есть наследник, который расширяет базовый. У каждого своя валидация, но в наследнике использовать валидацию базового класса было бы неплохо.
Как вариант, я могу сделать свойство MaxValue в наследнике не override, а new, со своим собственным полем поддержки. И валидацию базового класса для этого свойства вызвать методом ValidateMaxValue, а не через вызов родительского варианта сеттера base.MaxValue = value;
Сделал с new и отдельным вызовом валидирующего метода ValidateMaxValue для потомка. И ещё вынес валидацию CurrentValue тоже в отдельный метод. Вроде, так лучше
public class Param { double maxValue; public double MaxValue { get => maxValue; set => maxValue = ValidateMaxValue(value); } protected double ValidateMaxValue(double value) => ... } public class ParamVolatile : Param { double maxValue; public new double MaxValue { get => maxValue; set { maxValue = ValidateMaxValue(value); currentValue = ValidateCurrentValue(CurrentValue, 0, maxValue); } } double currentValue; public double CurrentValue { get => currentValue; set => currentValue = ValidateCurrentValue(CurrentValue, 0, MaxValue); } ValidateCurrentValue(double value, double min, double max) => Math.Clamp(value, min, max); }
как придумывал такую архитектуру,
вообще то называть это архитектурой у меня язык не поворачивается. Слишком мелко.
Типа в озере на доске катался, а после говорить как на море тренировался.
Что значит "вся логика в одном объекте"?
Ну вот же описан объект в нём и должно быть всё
public double CurrentValue { get; set; } public double MaxValue { get; set; }
Что значит "вся логика в одном объекте"?Ну вот же описан объект в нём и должно быть всё
public double CurrentValue { get; set; } public double MaxValue { get; set; }
Нет, базовый класс тоже вполне самодостаточный - можно создавать его объекты отдельно. Но часть его функциональности я бы хотел иметь в потомке. Гляньте мой последний вариант. Мне в нём не нравится только то, что свойство MaxValue в потомке почти полностью повторяет родителя. Может, вернуться к virtual-override и вызову базового сеттера, оставив лишь валидирующие функции?
public class Param { double maxValue; public virtual double MaxValue { get => maxValue; set => maxValue = ValidateMaxValue(value); } double ValidateMaxValue(double value) => ... } public class ParamVolatile : Param { public override double MaxValue { get => base.MaxValue; set { base.MaxValue = value; currentValue = ValidateCurrentValue(CurrentValue, 0, MaxValue); } } double currentValue; public double CurrentValue { get => currentValue; set => currentValue = ValidateCurrentValue(CurrentValue, 0, MaxValue); } ValidateCurrentValue(double value, double min, double max) => Math.Clamp(value, min, max); }
Ну да - в этом и смысла сокрытия. Вот сравнение двух вариантов. Даже подсветил вещи, которые поменялись. Я склоняюсь к правому. Это по сути тот же вариант, что у меня в первом посте, лишь валидацию в отдельный метод вынес и использую его явно, вместо неявного вызова через присвоение свойства самому себе.
Нет, базовый класс тоже вполне самодостаточный
ну тогда описание должно быть другим. Мы же видим только маленький описанный кусочек. А что там еще должно быть, знает только автор. Много раз на личном примере убеждался, что не просто описать описать всё сразу.
Я бы попытался бы вначале сформулировать что хочется. Иногда помогает себе же.
оставив лишь валидирующие функции?
Это кстати не совсем валидирующие функции. Валидирующие функции имеют право возвращать результат: валидный / невалидный объект
оставив лишь валидирующие функции?Это кстати не совсем валидирующие функции. Валидирующие функции имеют право возвращать результат: валидный / невалидный объект
У меня они больше корректирующие - принудительно корректируют значение. Дело в том, что это не валидация пользователя, которому сообщают, что значение неправильное и он должен его исправить. Тут значение приходит из другого объекта, и попросить его исправить не получится. Поэтому эти фукнции исправляют его сами.
Мне в последнем варианте (с зелёным) не нравится, что я два раза вызываю один и тот же код для currentValue: currentValue = ValidateCurrentValue(...). И всё лишь ради того, чтобы не было "странного" CurrentValue = CurrentValue, которое смотрится компактнее и лаконичнее. Но если вся валидация будет внутри метода ValidateCurrentValue, без добавок снаружи него, то вроде нормально - не придётся вносить одинаковые изменения в двух разных местах.
У меня они больше корректирующие - принудительно корректируют значениену вот именно, поэтому название валидация сбивает с толку.
Просто вы привыкли, что валидация, это когда юзеру сообщаешь, а он исправляет. А более общее определение валидации - это убедиться, что данные правильные. А как это будет - через юзера или принудительно - неважно.
Когда вы даёте юзеру просто текстовое поле, а нужно получить от него числа, то вы валидируете текст через парсинг, потом сравнение на диапазоны и прочее. А можно сразу в контрол встроить возможность ввода лишь цифр и принудительное выставление введённых чисел по границам, если числа выходят за эти границы. И это всё разные виды валидации, только одна - более многословная и требует кучу кастомного кода, а другая - более автоматизированная, со многими вещами, работающими из коробки (если этот контрол кто-то другой написал).
что я два раза вызываю один и тот же код для currentValueне вижу особых проблем в этом
Мне больше не нравится, что проперти имеют логику.
По классике в пропертях вполне делается валидация. Просто у меня зависимые проперти, поэтому некоторые из них валидируют не только себя, а и другие проперти. А проперти совсем без логики не имеют смысла.
А проперти совсем без логики не имеют смысла.ну да public поле будет гораздо лучше
В Unity 3D всё на публичных полях делается, что касается работы с их редактором:
public string myName = "none";
Это для облегчения сериализации, как они объясняют.
В варианте с new я отказываюсь от механики родительского свойства и пишу свою миханику - т.е. хранится в поддерживающем поле потомка. Тут мне нужно лишь свойство с тем же именем. В варианте с override - в поддерживающем поле родителя, но тогда мне не нужно отдельное поле в потомке.
В Unity 3D всё на публичных полях делаетсяЕсли кому нравится играться в навозе пусть играется.
Я говорю, у них на это есть своя причина - сериализация. И это только для классов, скажем так, view. Можно писать и привычным способом - с публичными пропертями и приватными полями. Но требуется немного костылей.
вообще то называть это архитектурой у меня язык не поворачивается. Слишком мелко.
Типа в озере на доске катался, а после говорить как на море тренировался.
Нифига, я уже морской волк! ))
CurrentValue = CurrentValue; // чтобы принудительно вызвать валидацию CurrentValueСразу не проходит reviewнужна валидация, значит делаем, а не вызываем side эффект с комментарием.
А те, кто ревью делают, понимают хоть, что происходит и зачем это нужно, или тупо по дядибобовским методичкам шпарят? )))
А то разное бывает. "На ноль делить нельзя" - иногда можно. "Поля не должны быть публичными" - если надо, то можно. "Земля вращается вокруг Солнца" - а мой личный опыт подсказывает, что это Солнце вращается вокруг Земли.
начал читать и вспомнил Библию:
Посрамились мудрецы, смутились и запутались в сеть: вот, они отвергли слово Господне; в чем же мудрость их?
Иеремия 8:9 — Иер 8:9
Как то слишком много умного в простейшей задаче.
Я предложил предложить своё предложение для этой "простейшей задачи". Никто не захотел. Напомню задачу:
- текущее и максимальное значения одного параметра разнесены на два класса, но т.к. это один параметр, то они связаны - текущее зависит от максимального (текущее не может быть больше максимального), но не наоборот
- для каждого значения своя валидация - с учётом первого пункта связности двух параметров
Разнесены на 2 класса, т.к. составной параметр построен на базе простого, состоящего из одного значения - использует его инфраструктуру (поля, свойства, методы и т.д.) и валидацию. Всё просто. Не какое-нибудь многостраничное техзадание. Можно на собесах этим мучить. Типа задачка на полчаса. От силы час. Два класса, два свойства - смотрите, не запутайтесь, "сеньёры и ценные специалисты с ниипацо стажем". )))
Единственный вопрос - нафига разнесены?
Я уже 10 раз написал - функционал максимального значения у составного параметра точно такой же, как просто значения у простого параметра. Т.е. в составном простое значение используется как максимальное, и добавлено текущее значение.
Ребята, что с вами, это просто два жалких класса с парой куцых свойств на двоих же. Чего вы их так испугались?.. Ну хорошо, вам не нравится наследование. Тогда что? В нашем ООПешном загончике не так много вариантов для комбинирования - наследование, включение, пачка статических методов... Я что-то важное упустил?
Может, будем городить интерфейсы или фабрику фабрик? Два-то класса это слишком сложно, а вот инжекция фабрики на интерфейсах - это пахнет месяцем мозгового штурма, километров тестов, и всё это на сеньёрских зарплатах. ))
функционал максимального значения у составного параметра точно такой же, как просто значения у простого параметра
Предположим, что нам это важно, тогда для любого параметра должен быть объект
public double CurrentValue { get; set; } public double MaxValue { get; set; }
Так что
если у нас есть параметр1 Value, то он должен иметь CurrentValue и MaxValue
и есть параметр2 MaxValue, то и он должен иметь CurrentValue и MaxValue
В данном случае подобного не наблюдается.
функционал максимального значения у составного параметра точно такой же, как просто значения у простого параметраПредположим, что нам это важно, тогда для любого параметра должен быть объект
public double CurrentValue { get; set; } public double MaxValue { get; set; }Так что
если у нас есть параметр1 Value, то он должен иметь CurrentValue и MaxValue
и есть параметр2 MaxValue, то и он должен иметь CurrentValue и MaxValue
В данном случае подобного не наблюдается.
У нас есть просто параметры, которые относительно постоянны (в пределах определённого времени), и быстро изменяющиеся параметры, у которых значение может быстро изменяться между нулём и определённым значением - эти последние я называю составными, т.к. состоят из текущего и этого определённого. Это определённое значение не есть максимальное значение вообще, т.к. оно тоже может изменяться, но не так быстро, как текущее, а так же, как и значение обычного параметра. Но для удобства я называю его максимальным. А чтобы использовать общий класс (с общей функциональностью) для этого значения, я называю его MaxValue и в классе обычного параметра.
Я бы мог поменять MaxValue на просто Value - тогда название значения в обычном параметре стало бы более осмысленным, но в составном - менее, т.к. CurrentValue меняется от 0 до Value. Но тут либо то, либо другое - где-то название будет не очень соответствовать.
Суть-то не в названиях, а в реализации.
Вангую, что такого есть какой-нибудь пэттерн типа Subscriber или Notifier.
"Ты чё умничаешь, ты пальцем покажи!"
А этот паттерн проще, чем мои пару десятков строчек, из которых треть - скобки?
У нас есть просто параметры, которые относительно постоянны (в пределах определённого времени), и быстро изменяющиеся параметры
Опять что то не из той оперы. Это скорее к расположению элементов ввода. Что то еще вы батенька скрываете
Я бы мог поменять MaxValue на просто Value - тогда название значения в обычном параметре стало бы более осмысленным, но в составном - менее
Это сразу говорит о том, что что то не в порядке.
Суть-то не в названиях, а в реализации.
реализация дело вторичное, главное "модель системы".
У нас есть просто параметры, которые относительно постоянны (в пределах определённого времени), и быстро изменяющиеся параметрыОпять что то не из той оперы. Это скорее к расположению элементов ввода. Что то еще вы батенька скрываете
Это не элементы ввода, а элементы отображения. Есть параметры, которые относительно постоянные и описываются одним числом, а есть которые изменяются быстро (расходуются и пополняются) - им нужно два числа - максимальное значение и текущее.
Например, скорость обозначается параметром с одним числом (текущая скорость), а топливо - параметром с двумя числами (макс. топлива и текущий объём). Чтобы не делать два совсем разных типа параметров, сделал базовый тип с одним значением, и второй унаследовал от него, добавив текущее значение.
А отчего, кстати у Param MaxValue отсуствует MaxValue
Где отсутствует? У меня присутствует. Это у ParamVolatile отсутствует поле maxValue, т.к. используется поле базового класса через свойство базового класса. У ParamVolatile свойство MaxValue это обёртка над базовым одноимённым свойством.
у Param MaxValue отсуствует MaxValue ... Где отсутствует? У меня присутствует.
у Param MaxValue отсуствует MaxValue ... Где отсутствует? У меня присутствует.
Пройдите по моей ссылке и попробуйте отследить выполнение кода, если присвоить свойству MaxValue объекта ParamVolatile какое-нибудь значение. Обратите внимание на
base.MaxValue = value;
объекта ParamVolatileречь об объекте Param
Ну так в Param есть MaxValue, играющее роль и Value. Если значение одно, то какая разница, как его назвать? А мне удобно назвать для совместимости с классом-наследником. Или назову его Value - тогда будет удобно в базовом классе, а в наследнике придётся приписать коммент, что Value играет роль максимального значения.
Если же я введу третью сущность (Value, MaxValue, CurrentValue), то это будет дублирование, т.к. Value и MaxValue играют одну роль и одинаково валидируются, и с ними одинаково обращаются клиенты классов Param и ParamVolatile. А вот клиентам будет путаница - все алгоритмы нужно перестроить, чтобы Value и MaxValue воспринимались одинаково. Проще в классах Param и ParamVolatile ввести одну условность и откомментить её, чем перелопачивать алгоритмы.
Переименуем MaxValue в Value в обоих классах. Лучше стало?
Нет конечно. Не следует торопиться. У нас пока еще нет реализации. Обсуждаются только проблемы "модели".
Похоже для Value нам еще нужен еще UpperLimit, так как LowerLimit всегда 0. /Специально выбрал какое то другое имя чтобы не путатся/
Похоже для Value нам еще нужен еще UpperLimit, так как LowerLimit всегда 0. /Специально выбрал какое то другое имя чтобы не путатся/
как я понял для каждого значения нужно еще и ограничение или нет?
Да. Любой Param или ParamVolatile имеет свой скажем так глобальный максимум и минимум, который они не могут превышать. И эти максимум и минимум я храню в валидирующем коде, а не в самих Param или ParamVolatile. А у ParamVolatile есть ещё текущий максимум, который он не может превышать, пока этот текущий максимум не изменится. У Param значение одно и оно редко меняется, поэтому оно валидируется "глобальными" максимумом и минимумом. У ParamVolatile же два значения - текущее, и текущий максимум - и они тоже валидируются через глобальные максимум и минимум, плюс текущее значение валидириуется через текущий максимум. Т.е.:
- и у Param, и у ParamVolatile все значения валидируются через глобальные валидаторы
- значение у Param и текущий максимум у ParamVolatile валидируются через глобальные валидаторы одинаково
- клиентский код работает со значением Param и с текущим максимумом ParamVolatile одинаково, т.к. эти значению играют одинаковую роль (если бы ParamVolatile не изменялся в текущих пределах, то всегда был бы равен текущему максимуму - т.е. аналогично просто значению из Param).
- текущее значение у ParamVolatile валидируется ещё и через текущий максимум
Это всё говорит о том, что при имплементации этих условий лучше бы для текущего максимума ParamVolatile унаследовать логику работы значения Param. И назвать их одинаково. А вот как конкретно - MaxValue или просто Value - другой вопрос, и больше к удобству или пониманию. Код работать будет с любым названием, если будут удовлетворяться все пункты выше.
По-моему, вы просто не можете себе представить, что за ParamVolatile такой может быть. А это просто - любое значение, которое расходуется и пополняется, и максимальная величина пополнения тоже может изменяться. И максимальная величина пополнения играет ту же роль, и так же обрабатывается многим клиентским кодом (например, он ожидает одинаковое имя свойства, а не делит их на два разных, не производит распознавание и ветвление), как и просто величина для значения, которое не расходуется и не пополняется, а просто есть. Я привёл пример скорости и запаса топлива. Скорость изменяется, но может быть и постоянной в течении какого-то времени, а топливо тратится непрерывно. Т.е. запас топлива более волатильная величина, чем скорость. Плюс топливо можно пополнить, но не более какой-то границы, а саму эту границу можно сдвинуть (у меня бак изменяемого объёма, или я канистры могу с собой взять и тасовать их - не важно).
Можно ещё сказать, что есть просто две величины - менее волатильная и более волатильная. И если менее волательную надо валидировать один раз при любом изменении, то более волатильную - дважды: текущее значение на две границы - волатильную и постоянную, и волатильную границу - на значение постоянной границы.
А вот клиентам будет путаница - все алгоритмы нужно перестроить,то есть есть еще одно ограничение о котором умолчали?
Да, но оно простое - имена значения Param и текущего максимального значения ParamVolatile должны быть одинаковыми. Сделай это, и клиентский код будет нормально работать.
вы просто не можете себе представить, что за ParamVolatile такой может быть
https://ru.wikipedia.org/wiki/Вола�%...
и не только это
Как всегда, в английском варианте больше и подробнее, и что разные волательности бывают. Я просто выбрал название, которое считаю более подходящим.
Я просто выбрал название, которое считаю более подходящим.
Когда долго живешь в проекте, то уже точно знаешь в какую дырку нужно вставить какую макаронину. И всё там кажется знакомым и понятным.
Когда же на это же самое смотрит кто то посторонний, то он обычно видит какую-то груду мусора.
Тоже самое было бы если бы и я какую то проблему рассказывал
Но уж если сильно хочется иметь "изменчивость", это скорее variability, changeability и море других. Хотя volatility тоже как бы в этом ряду, мне он воспринимается прежде всего как финансовый термин. Особенно на русском.
Если у меня есть параметр который имеет значение и если у меня есть набор параметров, то я буду всегда ожидать в наборе тоже самое значение, а не что то совсем другое. И мне абсолютно наплевать как часто что меняется. Данные это данные.
и у Param, и у ParamVolatile все значения валидируются через глобальные валидаторы
кажется, что Штирлиц начал давал показания, но нет это только видимость
Видимо речь о подобной функции - double ValidateParam(value, min1, max1)
хотя на самом то деле она делает что то типа этого TransformValueToRange и min1, max1 определяются в конкретном классе Param
клиентский код работает со значением Param и с текущим максимумом ParamVolatile одинаково
Это вообще абсолютно гениальное решение.
В одном случае мне выдают количество чего то, а другом максимум чего то
Есть подозрение, что с тоннами, килограммами и граммами - подобный бардак.
смысле "нестабильный, часто меняющийся" подобрано правильно
дело то не только в правильности. Хотя может это и мои личные тараканы. Подобное слово у меня ассоциируется с финансами и смотреть на него просто так, со стороны совершенно непривычно, в отличии от например unstable
Когда долго живешь в проекте, то уже точно знаешь в какую дырку нужно вставить какую макаронину. И всё там кажется знакомым и понятным.
Когда же на это же самое смотрит кто то посторонний, то он обычно видит какую-то груду мусора.
Тоже самое было бы если бы и я какую то проблему рассказывал
Но уж если сильно хочется иметь "изменчивость", это скорее variability, changeability и море других. Хотя volatility тоже как бы в этом ряду, мне он воспринимается прежде всего как финансовый термин. Особенно на русском.
Я хотел максимально абстрагироваться от конкретной реализации и темы. У меня в проекте эти классы с параметрами по-другому называются.
Если у меня есть параметр который имеет значение и если у меня есть набор параметров, то я буду всегда ожидать в наборе тоже самое значение, а не что то совсем другое. И мне абсолютно наплевать как часто что меняется. Данные это данные.
По-разному бывает. У меня у данных есть значения, с которыми одна часть кода работает (с максимальными), и есть часть кода, которая работает лишь с текущими значениями волатильных параметров. Например, та часть кода, что делает постоянный расчёт в цикле по нескольку десятков раз в секунду, работает с волатильными значениями, а то, что отрабатывает раз в несколько секунд или вообще по событиям - с максимальными значениями волатильных параметров или обычными значениями обычных параметров. Просто вы наверное не привыкли к таким областям программирования. В каком-то смысле это минус - узкий взгляд на вещи. )))
За спором о названиях упустили другую важную часть - переиспользование свойств базового класса. Вам такая запись не кажется незнакомой, чужеродной, странной?
public class Parent { double maxValue; public virtual double MaxValue { get => maxValue; set => maxValue = ValidateMaxValue(value); } } public class Child: Parent { public override double MaxValue { get => base.MaxValue; set { base.MaxValue = value; // use validation from base class // some additional code - can be additional child class validation also, like this: // base.MaxValue = ValidateMaxValueInChild(value); } } }
Просто вы наверное не привыкли к таким областям программирования.
Скорее я не привык к подобным извращениям
что делает постоянный расчёт в цикле по нескольку десятков раз в секунду
что отрабатывает раз в несколько секунд или вообще по событиям
Если производительность меня не мучает, то мне абсолютно наплевать какая часть кода как часто вызывается. Не должно быть никаких подобных зависимостей.
переиспользование свойств базового классаобычно это функции
Закрываю глаза на всё остальное
совершенно непривычно
я не привык к подобным извращениям
Не, ну вы прямо какой-то программерский консерватор-пуританен. Неужели не хотелось иногда пошалить, нарушить табу, выйти за рамки дозволенного? Или боитесь, что Дядя Боб придёт и поставит в угол за непослушание? )))
А зачем вообще дали возможность переопределять кучу разных членов класса, если пуритане говорят, что можно только методы и только в одной позе? ))
а просто код, работающий лишь с теми или другими частями параметров
Ну а на это тем более наплевать. Дай мне значение и дай мне максимум - это то что будет интересовать.
А не то что если параметр тип1 - получаем значение, а если тип2 получаем максимум.
клиентский код работает со значением Param и с текущим максимумом ParamVolatile одинаковоЭто вообще абсолютно гениальное решение.
В одном случае мне выдают количество чего то, а другом максимум чего то
В программе есть объекты, которые рассматривают значения обычных параметров и максимальные значения волатильных параметров как вещи одного рода. Например, объект, который "увеличивает показатель какого-то параметра" - для обычного параметра он увеличивает просто его значение, а для волатильного - его максимальное значение, а не текущее. А есть объекты, которые работают лишь с текущими значениями волатильных параметров.
Ну например, "бонусная карта члена клуба" - она увеличивает максимальную заправку (максимальное значение волатильного параметра) и даёт скидку (значение обычного параметра), но не увеличивает текущее значение топлива в баке (текущее значение волатильного параметра).
Или например объект "неисправность такая-то" - уменьшает скорость (значение обычного параметра) и максимальный заряд аккумулятора (максимальное значение волатильного параметра), но не влияет на текущий заряд аккумулятора. Примеры немного отфанарные, просто более реальные неохота придумывать.
А не то что если параметр тип1 - получаем значение, а если тип2 получаем максимум.
Вот чтобы этого не было, я и назвал свойства одинаковыми именами, но значения в них хранятся по сути: для обычных параметров просто значения, для волатильных - максимальные значения. У меня в проекте они названы не MaxValue, а просто Value. Но для задания вопроса я подумал, что MaxValue наверное понятнее будет, т.к. в варианте с просто Value у волатильного параметра тогда будут свойства Value и CurrentValue, что тоже может ввести в заблуждение.
Отнесение того или иного параметра к волатильному или нет (типа скорости или величины топлива в баке) зависит не от природы вещей и правдоподобных объяснений их поведений, а того, как код обрабатывает эти параметры, какие типы объектов есть в программе, влияющих на эти параметры.
Так я давно передал информацию, что надо сделать. Уже много раз так и сяк сформулировал. А вы спрашиваете постоянно - а зачем, а имеет ли это какое-то отображение на реальный мир, а что скажет Дядя Боб? )))
А вы спрашиваете постоянно - а зачем
Без полной "картины мира" многое кажется совершенно непонятным.
Возможно, что зная все, можно будет сказать - да других вариантов не видать.
По крайней мере, продолжение было воспринято именно так, что хочется узнать альтернативу.
Вы умеете мыслить абстрактно, без "полной картины мира"? Я написал, чего хочу, и предложил свой вариант исполнения. А вы не пытаетесь улучшить его или предложить свой, а постоянно спрашиваете, зачем мне это надо и почему я не делаю так, как привыкли вы.
Я даже представил один вариант "реальной картины мира", когда некоторые объекты могут влиять лишь на значения обычных параметров и максимумы волатильных параметров. Т.е. например та же "бонусная карта клиента" имеет список модификаторов на параметры, и эти модификаторы применяются в цикле типа такого
foreach(var mod in modifiers) foreach(var param in params) // params is a List<Param>, where Param and ParamVolatile objects are placed together if(mod.ParamType == param.ParamType) param.MaxValue += mod.Value;
Если бы param.MaxValue имело бы разное имя в классах Param и ParamVolatile (например Value и MaxValue соответственно), но одинаковое по смыслу, то мне бы пришлось усложнять код - вводить распознавание волатильного и неволатильного параметра, а присвоение значения модификатора делать всё равно одинаково, типа такого
if(param is Param) param.Value += mod.Value; else if(param is ParamVolatile) param.MaxValue += mod.Value;
В этом нет смысла. Но это лишь одно место, где пришлось бы так усложнять. А таких мест много. Если работа с Value и MaxValue идёт одинаково, т.к. это одно значение по смыслу, то проблема не в имплементации, а лишь в названии свойства. Я могу заменить его в любой момент без изменения кода. Вы же не можете взять в голову задачу лишь из-за смущающего вас названия свойства, хотя уже 10 раз объяснено, что это такое и зачем так сделано. Я вам написал, что можно заменить название MaxValue на просто Value - это вам поможет? Вы ответили, что нет - ведь теперь непонятно, чем отличаются Value и CurrentValue в ParamVolatile. Ну придумайте вариант лучше для названия свойства, но код-то от этого не поменяется.
Ещё можно поиграться с названиями классов. Скажем, не ParamVolatile, а ParamConsumable, или ParamExpendable, или ParamReplenishable. Вам бы это больше помогло? Свойства-то так бы и остались Value и CurrentValue или MaxValue и CurrentValue.
Меня вообще больше интересовал дизайн двух классов. И я спросил, что-то подобное видели или использовали? Видите какие-то минусы или варианты улучшения?
А куда это применить и как назвать - дело десятое.
Можно ли сделать лучше для описанной задачи (напомню - выделено жирным)? Может что с интерфейсами намутить?
Ничего мутить не надо.
Надо просто вынести валидацию в отдельную функцию и каждый класс должен валидировать то, за что он отвечает.
public class Param { double maxValue; public virtual double MaxValue { get { return maxValue; } set { maxVaslue = value; Validate (); } } protected virtual Validate () { } } public class ParamVolatile : Param { double currentValue; public double CurrentValue { get { return currentValue; } set { currentValue = value; Validate (); } } protected override void Validate () { base.Validate (); currentValue = Math.Clamp(value, 0, MaxValue); } }
Хотя нет, не то. Функция валидации у меня присваивает отвалидированное значение. У вас получается, что MaxValue остаётся неприсвоенным, а присваивается лишь CurrentValue. Тут надо больше править. Что-то типа того, что ниже.
public class Param { double maxValue; public virtual double MaxValue { get { return maxValue; } set { maxVaslue = value; Validate (); } } protected void virtual Validate () { maxValue = ... // maxValue validation } } public class ParamVolatile : Param { double currentValue; public double CurrentValue { get { return currentValue; } set { currentValue = value; Validate (); } } protected override void Validate () { base.Validate (); // maxValue validation currentValue = ... // currentValue validation } }
При этом зачем присвоения maxVaslue = value и currentValue = value, если мы будем их переприсваивать при валидации? А это явно из-за того, что нельзя передать параметр value в фукнцию Validate в обоих классах одинаково - т.е. сначала присвоить это значение полю, а потом поле отвалидировать внутри функции Validate. При этом функция Validate используется тоже запутанно - она ничего не возвращает, и будто поэтому ничего не меняет, а на самом деле меняет значения полей. Но мы не можем написать так, как в моём варианте maxValue = Validate(value), т.к. иначе придётся делать две фукнции валидации - для каждого свойства отдельно, каждая со своим набором параметров.
Я бы сказал, что у вас от моего варианта отилчается лишь тем, куда вы помещаете непонятную, запутанную часть кода, требующую пояснения. Вы это делаете в отдельной функции - ход её выполнение у вас нетривиальный. Зато свойства "чистые". У меня это в свойствах, а сами фукнции валидации "чистые".
Но по сути ничего не поменялось - как было у класса ParamVolatile свойство из базового класса, так и осталось, и его название будет смущать разных АлексНеков. )))
Вобщем, ваш вариант тоже имеет право на жизнь, и от моего отличается лишь переносом неочевидной части кода в функцию валидации, и дополнительной валидацией MaxValue при изменении CurrentValue, хотя это не нужно.
Прикол в том, что это нифига не вализация :)
По идее Validate должна кидать исключение :) Тут надо было бы подумать над названием функции... не знаю FitToRange или что там больше по смыслу подходит.
Я бы сказал, что у вас от моего варианта отилчается лишь тем, куда вы помещаете непонятную, запутанную часть кода, требующую пояснения.
Запутанная часть кода? :) Запутанная часть кода - это херь с new :) А тут все просто.
Вы это делаете в отдельной функции - ход её выполнение у вас нетривиальный. Зато свойства "чистые". У меня это в свойствах, а сами фукнции валидации "чистые".
Ход выполнения вполне тривиальный. И, при этом, никакого дублирования кода ;)
Но по сути ничего не поменялось - как было у класса ParamVolatile свойство из базового класса, так и осталось, и его название будет смущать разных АлексНеков. )))
Ну тут уж как ты задачу сформулировал ;)
Прикол в том, что это нифига не вализация :)
По идее Validate должна кидать исключение :) Тут надо было бы подумать над названием функции... не знаю FitToRange или что там больше по смыслу подходит.
Подумать над названием можно. Но я специально убрал конкретное исполнение, т.к. там может быть любая валидация, а не только приведение к диапазону. И что должна делать валидация - не установлено раз и навсегда кем-то. И уж тем более не обязательно кидать исключения. В некоторых фреймворках она просто возвращает статус и сообщение об ошибке. А в некоторых других, например встроенная в контролы валидация, принудительно исправляет введённые значения безо всяких сообщений и исключений - типа, при вводе в числовое поле числа, выходящего за пределы диапазона, число принудительно устанавливается в ту или иную границу этого диапазона. У меня ближе ко второму варианту.
Я бы сказал, что у вас от моего варианта отилчается лишь тем, куда вы помещаете непонятную, запутанную часть кода, требующую пояснения.Запутанная часть кода? :) Запутанная часть кода - это херь с new :) А тут все просто.
У меня нет "хрени с new" - это был один из вариантов, который не рассматривался далее. Далее я давал ссылки на вариант с override. Вы просто перегрузили метод валидации, а я перегрузил свойство. Других особых отличий нет. Ну ещё у меня не смешаны валидация MaxValue и CurrentValue, т.к. это разные валидации. У вас же вся валидация в одном методе, метод отвечает за валидацию всего класса и вызывается при изменении любого свойства из MaxValue и CurrentValue.
Вот наши варианты (ваш я подправил, т.к. maxValue надо присвоить отвалидированное значение в любом случае). Ваш слева, мой справа.
У меня, например, минус в том, что переопределение всего свойства MaxValue в классе ParamVolatile явно избыточно - достаточно переписать лишь сеттер. Но сеттер отдельно не переписывается, а городить для этого отдельный метод SetMaxValue, который вызывать в сеттере же свойства MaxValue - избыточно.
Вот я причесал выш код, как я бы его написал - два валидирующих метода вместо одного общего
public class Param { double maxValue; public virtual double MaxValue { get => return maxValue; set => maxValue = ValidateMaxValue(); } protected double virtual ValidateMaxValue() => ... } public class ParamVolatile : Param { double currentValue; public double CurrentValue { get => return currentValue; set => currentValue = ValidateCurrentvValue(); } override double ValidateMaxValue() { base.ValidateMaxValue(); ValidateCurrentvValue(); } double ValidateCurrentvValue() => ... }
Вот слева направо: ваш вариант, ваш, исправленный мной, мой собственный вариант. Вот посередине - ваш исправленный - мне нравится больше всего.
Я не знаю зачем надо валидировать MaxValue, ну тут уж хозяин - барин :)
Я также не совсем понимаю, каким образом тут будет установлено значение maxValue
public class Param { double maxValue; public virtual double MaxValue { get => return maxValue; set => maxValue = ValidateMaxValue(); } protected double virtual ValidateMaxValue() => ... }
если value из сеттера не передается :)
А вот эта конструкция
override double ValidateMaxValue() { base.ValidateMaxValue(); ValidateCurrentvValue(); } double ValidateCurrentvValue() => ...
необходима по одной простой причине: по какой-то неведомой причине валидации currentValue и maxValue должны иметь разные названия :)
Но спишем это все на "задачу в с тиле Murr'а", когда в процессе решения всплывает еще 100500 деталей :D
Я также не совсем понимаю, каким образом тут будет установлено значение maxValue
public class Param { double maxValue; public virtual double MaxValue { get => return maxValue; set => maxValue = ValidateMaxValue(); } protected double virtual ValidateMaxValue() => ... }если value из сеттера не передается :)
Это я опечатался - надо передать значение из сеттера. Ещё надо присвоить currentValue при валидации MaxValue в классе ParamVolatile, затем там же вернуть отвалидированное значение MaxValue. И убрать virtual у свойства MaxValue. И изменить метод ValidateCurrentValue, чтобы он мог принимать значение MaxValue. Мдаа, много изменить надо, чтобы ваш подход применить. Вот так будет правильнее
public class Param { double maxValue; public double MaxValue { get => return maxValue; set => maxValue = ValidateMaxValue(value); } protected virtual double ValidateMaxValue(double value) => ... } public class ParamVolatile : Param { double currentValue; public double CurrentValue { get => return currentValue; set => currentValue = ValidateCurrentValue(value); } protected override double ValidateMaxValue(double maxValue) { var newMaxValue = base.ValidateMaxValue(maxValue); currentValue = ValidateCurrentValue(CurrentValue, newMaxValue); return newMaxValue; } double ValidateCurrentValue(double currentValue, double maxValue) => ... }
по какой-то неведомой причине валидации currentValue и maxValue должны иметь разные названия :)
Попробуйте переписать мой последний вариант с одним методом валидации. У них же разный набор параметров. И в вашем случае у вас лишняя валидация - т.е. валидируются оба свойства, когда изменяется любое из них. Заметьте, у меня идёт переприсваивание значений полей при валидации. В вашем варианте изменения всех свойств при изменении любого из них придётся клиентам класса это учитывать - что изменится MaxValue при изменении CurrentValue и наоборот. А мне нужно лишь наоборот. Поэтому валидацию этих свойств лучше разделить и вызывать отдельно или одну за другой в зависимости от того, какое свойство изменилось.
У них же разный набор параметров.
Ну это уже твои тараканы :) Оба этих метода легко переписываются так, что у них вообще нет никаких параметров.
И в вашем случае у вас лишняя валидация
Нету там никакой лишней валидациию Эта функция:
protected virtual Validate () { }
ничего не валидирует и не изменяет. Она пустая.
Я вообще не понимаю, от куда в условии взялась необходимость валидировать/изменять maxValue (подход Murr'а? :D)
Да даже если вдруг и надо валидировать maxValue, то я не вижу в этом проблемы. Ну займет эта ненужная валидация лишких 3 такта. И что? Зато тот, кто будет использовать ParamVolatile будет уверен в том, что все поля валидны и сразу понятно где, что, как и по каким правилам меняется.
Да даже если вдруг и надо валидировать maxValue, то я не вижу в этом проблемы. Ну займет эта ненужная валидация лишких 3 такта. И что? Зато тот, кто будет использовать ParamVolatile будет уверен в том, что все поля валидны и сразу понятно где, что, как и по каким правилам меняется.
Изменение CurrentValue не влияет на MaxValue. Т.е. при изменении CurrentValue не нужно лишний раз удостовериваться, что MaxValue имеет правильное значение. У вас это получается случайно, т.к. хочется всю валидацию поместить в один метод. Но при масштабировании (вовлечении в валидацию большего числа свойств) это будет давать лишнюю нагрузку и захламлять усложнять метод Validate. В моём же подходе я буду просто тасовать вызовы валидирующих методов в нужном порядке по мере увеличения свойств, требующих валидации.
Ну и там не 3 такта. Как я сказал, клиенты класса тоже должны отреагировать, что изменилось больше свойств, чем планировалось. Но это мелочи.
Т.е. для текущей задачи ваш вариант подходит, и я принимаю его как правильный. Но для себя немного изменил его, чтобы для будущего лучше подошло, потому что, как вы правильно сказали, я знаю больше контекста, чем вы.
Т.е. при изменении CurrentValue не нужно лишний раз удостовериваться, что MaxValue имеет правильное значение.
Это очень спорное утверждение, т.к. Validate по идее должена валидировать весь объект.
У вас это получается случайно, т.к. хочется всю валидацию поместить в один метод.
Нет, никакой случайности тут нет.
В моём же подходе я буду просто тасовать вызовы валидирующих методов в нужном порядке по мере увеличения свойств, требующих валидации.
И в результате будет захламлен весь класс :) Т.е. на каждую проперти будет по специальному валидатору при этом с неизвестным заранее количеством вызовов. И самое забавное в том, что валидироваться будет только часть объекта, т.е. нельзя будет гарантировать, что объект находится в валидном состоянии :)
А если на все это наложить требование тестируемости, то станет совсем весело :)
Ну и там не 3 такта. Как я сказал, клиенты класса тоже должны отреагировать, что изменилось больше свойств, чем планировалось. Но это мелочи.
Если ничего не менялось, то и реагировать не нужно ;) Я уж не говою о том, что такого условия не было ;) Для оповещения клиентов должен быть свой механизм.
Т.е. при изменении CurrentValue не нужно лишний раз удостовериваться, что MaxValue имеет правильное значение.Это очень спорное утверждение, т.к. Validate по идее должена валидировать весь объект.
Нужно ли валидировать ёмкость бака при изменении уровня топлива по мере езды или при заправке?
Нужно ли валидировать ёмкость бака при изменении уровня топлива по мере езды или при заправке?
Не нужно. Более того, емкость бака неизменяемая величина, а значит у емкости бака не должно быть никакого сеттера или другой возможности изменить этот параметр :)
По мере езды или заправки - да. Но если мы заедем на веркштатт к знакомому Михалычу, который за бутылку приварит нам второй бак с возможностью перекачки... Или просто заменит нам бак на более ёмкий... Вобщем, совсем уж от сеттера избавляться не надо, т.к. на более глобальном уровне и ёмкость бака может измениться. В условии я просто обозначил, что это тоже должно иметь сеттер, а не быть там константой или одним лишь геттером.
Переписал с одним методом валидации так. С учётом, что мне надо в методе валидации обновить отвалидированное значение изменяемого свойства, используя при этом правильные свойства, с учётом уровня доступности полей (это я про "currentValue = ... // validate currentValue using MaxValue, currentValue"). Т.е. сам метод валидации ничего не возвращает, а все присваивания полям делает внутри себя.
public class Param { double maxValue; public double MaxValue { get => maxValue; set { maxValue = value; Validate(); } } protected virtual void Validate() { maxValue = ... // validate maxValue using maxValue } } public class ParamVolatile : Param { double currentValue; public double CurrentValue { get => currentValue; set { currentValue = value; Validate(); } } protected override void Validate() { base.Validate(); currentValue = ... // validate currentValue using MaxValue, currentValue } }
Вобщем, совсем уж от сеттера избавляться не надо, т.к. на более глобальном уровне и ёмкость бака может измениться.
Не может. Может меняться конфигурация, т.к. можно вызывать конструктор бака с одним или с другим параметром (объемом). Однако объем бака - все равно не меняется.
Теперь последний штрих - справа мой вариант без перезаписанного свойства, но с отдельными методами для каждого свойства. Слева ваш - без перезаписываемого свойства, но с одним методом для всех свойств.
Думаю, тут зависит от вкуса или задачи - валидировать все свойства при любом изменении любого свойства, или валидировать лишь зависимые свойства, как в моём варианте. Если свойств много, то ваш метод Validate превращается в лапшу, а мои отдельные методы всё ещё сохранают читаемость.
Но если нужен признак валидности всего объекта, то у вас достаточно вызвать один метод, а мне - завести специальный метод на весь объект, где вызвать пачку моих отдельных методов. Или завести дополнительные поля типа bool isValidMaxValue для каждого свойства, в каждом отдельном валидирующем методе их устанавливать, а в общем валидирующем методе для всего объекта - считать их по типу bool isValidObject = isValidProp1 && isValidProp2 && isValidProp3...
Вобщем, совсем уж от сеттера избавляться не надо, т.к. на более глобальном уровне и ёмкость бака может измениться.Не может. Может меняться конфигурация, т.к. можно вызывать конструктор бака с одним или с другим параметром (объемом). Однако объем бака - все равно не меняется.
А если всё таки может, то как тогда спроектировать свойство или параметр "объём бака"?
Допустим, у меня бак новой экспериментальной модели - с изменяемым объёмом. И в нём плещется топливо. И бак может изменить свой объём прямо во время движения или по мере заправки. НАДА!!
Кстати, нашёл ещё один минус в единственном методе Validate на весь класс. Если свойства взаимозависимы, то нужно соблюдать порядок их валидации и присвоений. Если это всё в одном методе, а свойств много, то надо перетасовывать валидации ВСЕХ свойств. А если взаимозависимые свойства сгруппированы в отдельные методы валидации, то перетасовывать надо будет только в этих методах, что куда проще. Сами же эти отдельные методы валидации могут быть применены для всего объекта в любом порядке. Но это так, планы на дальнейшее развитие.
Вы умеете мыслить абстрактно
Похоже у нас совершенно разное определение абстрактного мышления.
То, что хочется вам, что то типа этого – а ну быстро копать яму от забора и до обеда.
Или с большой лупой ползать по земле пытаясь найти нору по следам жителя, вместо того чтобы осмотреть всё пространство с гораздо большей высоты.
Я написал, чего хочу
В итоге то выяснилось, что хотелки и исполнение несколько другие на самом деле, чем ожидалось.
Да и хотелки то совершенно странные.
А вы не пытаетесь улучшить его
Так улучшать то особо и нечего. Когда видишь «неправильную» / непонятную архитектуру нужно разобраться вначале в ней. Уже много раз попадалось, вроде всё правильно сделано и работает, но… не в том месте.
«Это неправильные пчёлы! И они, наверное, делают неправильный мёд!»
и почему я не делаю так, как привыкли вы
Вроде уже давно выяснили, что живем на разных островах с совершенно разными подходами к устройству мира. То, что кажется совершенно нормальным на одном острове считается абсолютно неприемлемым для другого. Поэтому чтобы понять действия человека с другого острова совершенно недостаточно знать только то что он хочет. Иначе его действия кажутся совершенно неадекватными. Нужно вначале понять отчего он хочет именно этого странного.
В этом нет смысла. Но это лишь одно место, где пришлось бы так усложнять.
Опять таки, смотря кому, для того кто будет следующий на правку это было бы более понятно. И не нужно ничего усложнять
Так смотрелось бы немного по другому
param.AddMaxValue(mod.Value)
Вы же не можете взять в голову задачу лишь из-за смущающего вас названия свойства
Нет, работа системы видится абсолютно дикой.
хотя уже 10 раз объяснено, что это такое и зачем так сделано
Эти все "объяснения" лежат на самом нижнем уровне и они не объясняют а все лишь пытаются объяснить зачем нам хочется так приспосабливаться к тому что уже есть. А вот какого так есть, так и осталось непонятным.
Вам бы это больше помогло?
Абсолютно нет, достаточно было понять что на самом деле хотелось сказать. Объект типа "А" и объект типа "Б" было даже более понятно
А куда это применить и как назвать - дело десятое.
Опять таки, с какого уровня смотреть.
Вот жилец дома говорит, а что скажете о дизайне этих двух ящиков? Отвечают, да нормально - тут можно еще финтифлюшку приварить, а тут и ручка не помешала бы и т.п.
А тот кто дом проектировал говорит - так первый ящик вообще ни в одну дверь не войдет, а второй превышает номинальную расчётную нагрузку на перекрытия.
Грубо конечно, но просто взять деталь из одного места и поставить ее в другое не всегда может быть возможным, особенно когда видно, что деталь оочень странная.
т.к. там может быть любая валидация, а не только приведение к диапазону
Для меня термин валидация выглядит скорее так
https://ru.wikipedia.org/wiki/Вали�%...
Важно, что валидация не может ничего менять в принципе. Она только анализирует данные на соответствие определенным правилам.
Что делать после этого - шаг номер 2
Хотя да, подобрать название для совмещенных двух шагов непросто.
Я написал, чего хочуВ итоге то выяснилось, что хотелки и исполнение несколько другие на самом деле, чем ожидалось.
Да и хотелки то совершенно странные.
Вот два последних примера на картинке удовлетворяют мои хотелки с первой страницы.
У вас и волательность не волательность, и валидация не валидация... )))
Я уже писал - контрол, который автоматически приводит данные к заданному валидацией диапазону - валидирует эти данные или нет?
У вас и волательность не волатильность, и валидация не валидация
Увы, мы видим мир через разные очки. Я пытался его увидеть через ваши очки, но не получилось вижу что типа этого
контрол, который автоматически приводит данные к заданному валидацией диапазону - валидирует эти данные или нет?
Нет конечно - он приводит данные к заданному валидацией диапазону. А то что один из шагов и есть собственно валидация данных, не даёт нам основания называть все операции вместе валидацией данных.
Иманна. Валидация это проверка. Которая или возвращает true/false отвечая на вопрос"верны ли данные", или бросает исключение. Но никак не изменяет данные.
Хотим привести к "верному" состоянию, называем хотя бы "makeValid". В немецком используют глагол "plausibilisieren"
Ладно-ладно, пусть будет любая -ация, хоть охренибилизация. Код-то от этого не изменится. Свойства тоже как хотите называйте - максВелью, хераксВелью. "Каррент велью не должно быть больше херакса" - звучит? В коде там от этого тоже ничего не поменяется. Главное, что код работает как мне нужно, а переименовать я потом могу по вкусу. Сейчас меня устраивает.
)))
Кстати, насчёт "назови А или B" - там на всяких Stackoverflow всякие Джоны Скиты и Эрики Липперты жалуются, что некоторые называют классы и объекты A и B вместо скажем Animal и Tiger или foo и bar. Да кто они такие ваще против тутошних титанов?! Привыкли понимаешь там на своём гейзападе узко мыслить, а мы тут доморощенные математики абстрактные - нам подавай "треугольник а бэ цэ равен треугольнику а-штрих бэ-штрих цэ-штрих". Мене, мене, текел, упарсин.
Меня удивляет, что вы не можете двигаться дальше, пока не получите идеальное название, полностью совпадающее с вашими представлениями.
Допустим, у меня бак новой экспериментальной модели - с изменяемым объёмом. И в нём плещется топливо. И бак может изменить свой объём прямо во время движения или по мере заправки. НАДА!
Любые ограничения можно ввести при помощи интерфейсов.
Ты высасываешь примеры из пальца "на лету". Банальный пример, который бы тут подошел - XML де- сериализация параметров бака. В этом случае необходимы и геттер и сеттер на объекте. Решение простое - надо работать на уровне интерфейсов.
Кстати, нашёл ещё один минус в единственном методе Validate на весь класс. Если свойства взаимозависимы, то нужно соблюдать порядок их валидации и присвоений. Если это всё в одном методе, а свойств много, то надо перетасовывать валидации ВСЕХ свойств. А если взаимозависимые свойства сгруппированы в отдельные методы валидации, то перетасовывать надо будет только в этих методах, что куда проще. Сами же эти отдельные методы валидации могут быть применены для всего объекта в любом порядке.
В твоей логике есть один изъян :)
Предположим у нас есть 3 своства: A, B и C. При этом A и B зависят от C.
С твои подходом нужно сделать 3 + 1 функцию: ValidateA, ValidateB, ValidateC и Validate
Выглядеть это будет так:
private void ValidateA () { ValidateC(); a = .... } private void ValidateB () { ValidateC(); b = .... } private void ValidateC () { c = .... } public void Validate () { ValidateC(); ValidateA(); ValidateB(); }
Очевидно, что ValidateC будет вызвана аж целых 3 раза :) Можно сократить количество вызовов ValidateC до двух, но тогда будет неявная валидация С.
Можно конечно пойти еще дальше и сократить количество вызовов до 1, но тогда придется писать дополнительный код.
Ну и все это безобразие должно конкурировать с этим:
public void Validate () { // a and b depends on c. Thats'y c must be validated before!!! c = .... <span class="redactor-invisible-space"></span> a = .... <span class="redactor-invisible-space"></span> b = .... }
Я не знаю что тут надо перетасовывать, но простота кода при моем подходе, как мне кажется, очевидна :)
Любые ограничения можно ввести при помощи интерфейсов.
Ты высасываешь примеры из пальца "на лету". Банальный пример, который бы тут подошел - XML де- сериализация параметров бака. В этом случае необходимы и геттер и сеттер на объекте. Решение простое - надо работать на уровне интерфейсов.
Почему именно интерфейсов? Почему не включать объекты, добавляя функциональности? Типа
List<BaseProperty> Properties
где BaseProperty может быть скол угодно сложным или просто базовым классом для добавления той или иной функциональности.
Мне интерфейсы напоминают модель классов в С++ - надо иметь так называемые заголовки и реализацию, желательно в разных файлах. Лишний гемор и усложнение, если применять интерфейсы именно для цели навешивания функциональности.
Я не знаю что тут надо перетасовывать, но простота кода при моем подходе, как мне кажется, очевидна :)
Мне надо попробовать дальше поприменять. Может ваш подход и окажется лучшим. Пока его и оставлю - пусть будет один метод.
Хотя справедливости ради, метод-то уже не один - из-за наследования минимум он имеет перегрузку. Но в подходе с кучей методов, если добавить наследование и перегрузки, мусора и лапши будет ещё больше. У меня пока лишь два класса в иерархии наследования, поэтому пока что всё выглядит не таким уж сложным.
Почему не включать объекты, добавляя функциональности?
Потому что на уровне абстракций все выглядит четче :)
Я уж не говорю о тестируемости.
List<BaseProperty> Propertiesгде BaseProperty может быть скол угодно сложным или просто базовым классом для добавления той или иной функциональности.
Тут есть одна проблема - со списком пропертей можно сделать все, что угодно.
Можно конечно полагаться на разумность того, кто будет использовать, но лучше все таки ограничить полет его фантазии до
IEnumerable<IBaseProperty> Properties { get; }
Я не знаю что тут надо перетасовывать, но простота кода при моем подходе, как мне кажется, очевидна :)
И это всё, конечно, очень красиво. Но не жизнеспособно. Как только класс становится немножко сложнее и таскает пару десятков полей, случается ой. И дело даже не в том, что один метод валидации внезапно становится строк так 300, с этим можно бороться, а с тем, что при изменении одного параметра мы валидируем и 29 остальных. Хотя они не менялись. И понеслась. Метод начинают "оптимизировать". Типа - а если вот это так, то вот эту часть можно не проверять. И он превращается в такой клубок if-else что легче повеситься, чем разобраться.
А вот если валидировать после каждого изменения, то валидация остаётся простой. Поменялся А - я проверяю не стал ли он больше Д и не меньше 0.19 * Ф. И ускоряется.
Только вот в прошлом месяце поменяли такой "супер-валидатор" на валидацию после каждого изменения. Время исполнения одного загрузочного теста уменьшилось с 130 секунд до 28. За время выполнения теста валидация вызывалась примерно 200 тысяч раз.
ПС и теперь надо уговорить поменять валидатор для целого кластера "договор". Код вынесен в классы. Вызывается при каждом изменении (блядь!). В сумме примерно 6 тысяч строк. В гуях есть одна кнопочка, при нажатии на которую для одного договора валидация вызывается 3,5 тысяч раз. 40 секунд ждём реакции. По профайлеру из низ 39 проводим в валидаторах. Ужасные истории на ночь. (№;%:!"% привычно откликнулось эхо).
Когда число пропертей разрастается, нужно рефакторить. Для малого числа удобнее в одном валидаторе на весь класс, для большого - как-то группировать зависимые свойства и валидировать их в группах их зависимости. Ну и общий валидатор для всего объекта тоже обычно присутствует. Главное, что нет одного универсального решения на все случаи, которое одновременно простое и для малого числа сущностей, и при сколь угодном масштабировании. В лучшем случае что можно придумать, чтобы не захламлять - использовать сторонние универсальные валидаторы, типа тех же атрибутов и фреймворков валидации для них, которые делают всю грязную работу, а ты лишь проверяешь свойства типа Property.Valid.
Отвлечёмся немного на другие "рахитекурты". Ещё одно подтверждение, что многие паттерны-шматтерны это костыли для старых говноязыков и куцых фреймворков, служащие для обхода их ограничений и недостатков. Когда чел городит репозиторий, фасад и юнит оф ворк вокруг Entity Framework, хочется спросить, из какого тифозного загона он сбежал. Какой-нибудь плюсовик поди? ))) А он ведь это на автоматизме уже делает, начиная любой новый код.
Вот что это за херь, которая делает из двух строчек одну на любую операцию? А если разобраться, то SaveChanges можно вызывать не на каждый чих, а пореже, в конце группы операций, и тогда вообще получается, что весь ниипаца-паттерн у нас - просто однострочные обёртки над уже существующим из коробки функционалом. Но для желающих накодить километры неподдерживаемой лапши "только я знаю, как это работает", чтобы забронзоветь и дотянуть до пенсии на этом месте - сойдёт.
Да вы просто неправильно его готовите, чуваки. В нашем паттерне главное отличие начинается с пятой строчки снизу, двадцатый знак слева в списке основных свойств нашего паттерна. А так он - копия другого паттерна, только сбоку. Там ниже совсем упоротые примеры по переименованию методов изкоробочного фреймворка и оборачиванию их вызовов. Уникальный шматтерн, мать его! И ведь все эти гении где-то работают, зачастую не на последних должностях. По сути всё сводится к тому, что приходя на работу куда-то ты либо соглашаешься с говнокодом местного гуру и пытаешься подражать его писулькам, либо валишь с позором из этой конторы. Искать там логику, какие-то перспективы - бесполезное занятие, зря тратящее нервы. И чем дольше в чей-то говнокод будешь погружаться, тем больше будет сужаться твоё мировоззрение и подходы.
У нас кстати тоже подобный паттерн довели до абсолюта - для любой операции существует свой код. Сохраняем объект так-то - одни код, сяк-то - другой. Там ещё можно их комбинировать. Поначалу казалось неплохо, а потом этих операций развелось... и отличаются друг от друга для одной сущности в небольших деталях. Потом на проект приходит новичок, и не понимает, какую правильную операцию из пары десятков выбрать для какой задачи. На вид они все похожи. Ну и начинает делать свой двадцать первый вариант, подсматривая поведение из предыдущих двадцати. Юнит оф ворк, мать его. Если бы тупо всё писалось по месту применения - в каждой форме или её модели, без отсылок к слою паттернов-шматтернов, было бы то же самое, но без лишнего слоя или двух.
Какие такие шматтерны? Это вообще никого не интересует. Тут начальнички всех мастей грызуться и сходят с ума, и скоро похоронят всю индустрию нафиг. Понравились фразы из комментов: "Не связано ли это с тем, что весь крупняк за последние 15 лет в той или иной степени сросся с Левиафаном и перенял у него склонность к сохранению лояльных, а не высокопрофессиональных кадров?", "ПМ просто бегает вокруг команды и следит, чтобы с лица заказчика не слезала улыбка". Только одно непонятно - на всё это кто-то должен слить прорвы денег. Кого кидают?
а переименовать я потом могу по вкусу.
Из опыта - подобное обычно не наступает никогда.
Вот интересно узнать ваше мнение: сделали мне ревью кода, а я говорю - некогда фигней маяться, завтра сдавать, и так постоянно. Это правильно? Куда придём в итоге?
Главное, что код работает как мне нужно
Есть только надежда, что когда то вы поймете, что главное не в работающем коде, как мне сейчас нужно.
Главное, что код работает как мне нужноЕсть только надежда, что когда то вы поймете, что главное не в работающем коде, как мне сейчас нужно.
Вы щас опять со своего острова, где все проекты на 100% вперёд проработаны и все знают изначально, чего хотят. А у меня пока непонятно, во что это выйти может - разработка экспериментальная. Может, я объединю эти свойства, а может добавлю других.
где все проекты на 100% вперёд проработаны и все знают изначально, чего хотят
У вас несколько искаженное представление о нашем острове.
Но у нас на любом этапе есть некая система, а не так - хряк, хряк, хряк и готово. И где каждый отдельный хряк еще долго обдумывается.
Может, я объединю эти свойства, а может добавлю других.
опять таки, речь идет о конкретной части, а не о всей системе.
Попробуйте чисто для себя, лично, задуматься о всей системе и как бы вы ее спроектировали "правильно". Есть большое подозрение, что подобных классов бы не понадобилось в принципе.
где все проекты на 100% вперёд проработаны и все знают изначально, чего хотятУ вас несколько искаженное представление о нашем острове.
Но у нас на любом этапе есть некая система, а не так - хряк, хряк, хряк и готово. И где каждый отдельный хряк еще долго обдумывается.
А бывает, что надо срочно хрякнуть, без обдумываний? Ну или вдруг захотелось запрещёночки?
Попробуйте чисто для себя, лично, задуматься о всей системе и как бы вы ее спроектировали "правильно". Есть большое подозрение, что подобных классов бы не понадобилось в принципе.
Нельзя правильно подумать о том, о чём пока не имеешь полного представления. Ну подумал, сделал, потом переподумал и решил сделать по-другому. Мутить полную технологическую канву под это дело, типа техзадание, дизайн-документ, архитектуру, тесты, и лишь потом писать код - это и бессмысленно, и кайфа нет. Прямой путь к забросу проекта. И вообще, у нас же аджайл в моде - делаем по велению левой пятки в темпе спринта, потом по велению правой пятки.
Вы не понимаете. Я свой проект разрабатываю как прототип, и чтобы было самому интересно. Я сделал что-то, что мне хотелось больше всего первым попробовать - понравилось. Думаю, хочу добавить это и это - снова понравилось. Потом хочу ещё что-то - архитектура не позволяет - исправляю архитектуру и все прежде сделанные фичи, если исправления их затрагивают. Т.е. проект пока лишь прототип, и много всего не сделано, то так можно довольно долго и много экспериментировать. Когда наделаю фич, что мне будет казаться достаточным, что проект стал неким законченным, с достаточной функциональностью, я это всё более-менее зафиксирую и напишу тесты и прочее подробнее. А сейчас я тесты пишу лишь для основной механики и математических функций.
Работать по заранее сделанному плану можно, если подобный проект хотя бы раз уже делал и знаешь, что надо реализовать в костяке. А я пока не знаю. У меня есть просто идеи, видение, как это должно выглядеть в идеале, но в коде реализации нет. Вот я шаг за шагом делаю прототип, приближающийся к моему видению. Пока получается. Главное, что мне нравится, и почти нет такого, что неделями, а то и месяцами делаешь какую-то неинтересную нудятину.
А бывает, что надо срочно хрякнуть,
Бывает, но всё равно думаешь перед тем как делать
Ну или вдруг захотелось запрещёночки?
Зачем?
Нельзя правильно подумать о том, о чём пока не имеешь полного представления.
Очень даже можно, может быть и не на 100%
Ну типа, перед тем как строить дом делаем для него фундамент. Какие будут окна и крыша фиг его знает сейчас.
типа техзадание, дизайн-документ, архитектуру, тесты
когда то было и такое
и кайфа нет
У кого как Кайф как раз от хорошей архитектуры.
у нас же аджайл в моде - делаем по велению левой пятки в темпе спринта
опять тема для баальшой дискуссии
Я свой проект разрабатываю как прототип, и чтобы было самому интересно
ну именно так для себя и делаю постоянно.
хочу ещё что-то - архитектура не позволяет
Бывает конечно, но на любом этапе она должна быть "правильной". Что под этим понимать будет конечно у каждого по разному.
Но в любом случае это первое о чем думаешь. Код вообще не интересен.
Главное, что мне нравится,
ну так это и есть самое главное. Если делаешь для себя то всё остальное вообще полная ерунда. Хотя всё равно получается так как для других бы делал
Вы не понимаете.
Скорее всего, поэтому и хотелось знать больше. Понять другого человека очень даже не просто.
Видишь абсолютную ерунду делает, а как разберёшься, понятно что с его точки зрения все правильно. Что с этим делать уже другой вопрос, на который часто нет ответа.
Когда наделаю фич
Похоже у нас просто разные подходы к проектированию. У вас похоже снизу вверх, а у меня очень часто наоборот.
За время выполнения теста валидация вызывалась примерно 200 тысяч раз.
Я конечное многое могу понять, но зачем валидировать весь объект после каждого изменения?
В гуях есть одна кнопочка, при нажатии на которую для одного договора валидация вызывается 3,5 тысяч раз.
Ну так сами себе злобные Буратины :) Что мешает вызывать валидацию один раз? Одна кнопка - одно нажатие - одна валидация.
Многое зависит от контекста, но давай возьмем простой пример: есть некий объект с множеством зависимых друг от друга пропертей. Этот объект пересылается от А к Б в виде XML/JSON и потом восстанавливается. В общем случае, валидировать при изменении проперти (при десериализации) нельзя, т.к. мы не можем гарантировать, что сначала будут десериализованы "базовые" проперти, а потом "зависимые". Следовательно после десериализации
мы должны иметь возможность валидировать весь объект.
Я конечное многое могу понять, но зачем валидировать весь объект после каждого изменения?
А вдруг? Потому что каждая операция "независима" и не знает ничего о том кто вызывался до нее, а кто после. И она "согласованная" (consistent). Т.е. результатом каждой операции должен быть валидный объект. А значит что? Каждая операция после изменений валидирует. А пока был один большой метод, он и отрабатывал. После каждой операции.
Ну так сами себе злобные Буратины :) Что мешает вызывать валидацию один раз? Одна кнопка - одно нажатие - одна валидация.
Не-не. Кнопка-то одна, и "пользовательское действие" одно - "ausfertigen". А сам процесс, который это нажатие запускает, если расписать то 5 листов в пдф получается. И после каждой операции валидируем. Чтобы "вовремя остановиться". Ведь каждая операция у нас что? Независимая и согласованная. Прям как транзакция. И её можно использовать в куче других процессов. И это правильно, да. Но по уму валидировать надо не всё в бооольшой сети объектов. А только изменения. Добавил застрахованное здание - мне не надо валидировать 10 других зданий, только добавленное.
Этот объект пересылается от А к Б в виде XML/JSON и потом восстанавливается. В общем случае, валидировать при изменении проперти (при десериализации) нельзя, т.к. мы не можем гарантировать, что сначала будут десериализованы "базовые" проперти, а потом "зависимые".
Очень даже можно. Начиная от "подсказок" в каком порядке вызывать сеттеры десериализатору и кончая написанием своего десериализатора. Получил от JSON-а мэп и десериализируй как тебе надо. Я это, конечно, про явовские jackson и gson, но думаю в шарповских библиотеках всё достаточно похоже.
Иногда и циклическая зависимость появиться может. А зависит от Б, Б от В, а В снова от А. Тогда прячем сеттеры, в сеттерах не валидируем, а делаем подходящую по смыслу операцию, которой одновременно передаются и А, Б и В. Ну а если всё ещё сложнее (что только не придумают бвл-щики, лишь бы не думать) то говорим - сорян, сам объект ничего не гарантирует, валидация только общая (всего объекта целиком), и не вызывается при каждом изменении.
В общем я это к чему: валидацию всего объекта в одном методе (классе) я теперь делаю только тогда, когда это внешняя валидация. Не внутри самого класса. И когда валидируемый класс никаких обещаний о своей "верности" не делает (в документации).
За время выполнения теста валидация вызывалась примерно 200 тысяч раз.Я конечное многое могу понять, но зачем валидировать весь объект после каждого изменения?
В вашем примере в мной немного переписанном вашем примере (справа) на каждое изменение свойства Param или ParamVolatile вызывается валидация всего объекта - оба свойства валидируются. Я же предлагал ввести отдельные методы для валидации зависимых свойств. Т.е. если 2 свойства зависимы, то оба получают один валидатор, внутри которого проверяются оба эти свойства. Не нужно делать все возможные комбинации проверок, а только отдельных независимых свойств и зависимых свойств.
Этот объект пересылается от А к Б в виде XML/JSON и потом восстанавливается. В общем случае, валидировать при изменении проперти (при десериализации) нельзя, т.к. мы не можем гарантировать, что сначала будут десериализованы "базовые" проперти, а потом "зависимые". Следовательно после десериализации мы должны иметь возможность валидировать весь объект.
А зачем вам валидировать объект при сериализации-десериализации? Защита от изменения сериализованного объекта при его передаче? Или вы тестируете фреймворки сериализации, правильно ли и без ошибок они работают?
У некоторых до маразма доходит - любой слой валидирует объект, даже если это всё в пределах одно оперативной памяти происходит. "На всякий случай". Если вы в такой враждебной среде запускаете код, что боитесь изменения при использовании даже оперативки, то тут явно другой подход нужен, а не бесполезная в этом случае валидация на каждом шаге.
Я вообще предлагал в типичных приложениях делать лишь валидацию на сервере при важных операциях, типа записи в БД, а пользовательский ввод задавить в рамки принудительной валидации, когда контролы просто не позволяют вводить неправильные значения. Если нужна защита не от дурака или случайности, а от специального вредителя, то это не валидацией делается.
Очень даже можно. Начиная от "подсказок" в каком порядке вызывать сеттеры десериализатору и кончая написанием своего десериализатора. Получил от JSON-а мэп и десериализируй как тебе надо. Я это, конечно, про явовские jackson и gson, но думаю в шарповских библиотеках всё достаточно похоже.
Конечно всё там есть. И полностью кастомная сериализация-десериализация есть.
https://www.newtonsoft.com/json/help/html/P_Newtonsoft_Jso...
https://www.newtonsoft.com/json/help/html/JsonPropertyOrde...
Иногда и циклическая зависимость появиться может. А зависит от Б, Б от В, а В снова от А. Тогда прячем сеттеры, в сеттерах не валидируем, а делаем подходящую по смыслу операцию, которой одновременно передаются и А, Б и В.
Некоторые сериализаторы могут сохранять ссылки на объкты - один раз сохранил объект в формате сериализации, и потом везде на его копии ссылки вставляются. Я сделал проще - у меня БД на клиенте, и она небольшая, поэтому к свойствам с объектом MyType MyObject я просто добавляю его айдишник int MyObjectId, который и сериализую (а сам объект - нет). Близко к сохранению ссылки на объект в формате сериализации.
Ну а если всё ещё сложнее (что только не придумают бвл-щики, лишь бы не думать) то говорим - сорян, сам объект ничего не гарантирует, валидация только общая (всего объекта целиком), и не вызывается при каждом изменении.
Ещё вариант - при десериализации присваивать значения полям поддержки свойств, а не самим свойствам. Т.к. валидация запускается обычно через свойства, то она не будет вызываться, если в конструкторе не вызывать их сеттеры.
Сериализовать и десериализовать айди вместо всего объекта просто. Для коллекций там сложнее. У меня есть такая конструкция. Тут бизнес логика работает лишь со свойством Items (комментарий "for business logic"), а всё остальное - обвязка. Приватные члены не сериализуются (если не обозначить атрибутом JsonProperty). Сериализуется лишь список айдишников ItemIds, который просто проходится по списку предметов, выбирая их айдишники. Для бизнес логики обвязка в виде ItemIds не используется, хотя может. Вместо ленивой инициализации можно использовать инициализацию в конструкторе. Но репозиторий тогда тоже должен быть доступен в нём же - т.е. в процессе десериализации. А это иногда нетривиально сделать. Поэтому ленивая загрузка - один из выходов.
// for deserialization and lazy initialization of Items List<int> itemIds; // for serialization [JsonProperty] List<int> ItemIds => Items.Select(i => i.Id).ToList(); // for lazy initialization of Items and as a backing field for it List<Item> items; // for business logic [JsonIgnore] public List<Item> Items => items ??= itemIds ?.Select(/* obtain item from somewhere by its id */) .ToList() ?? new List<Item>(); [JsonConstructor] public MyClass(List<int> itemIds) { this.itemIds = itemIds; }