Deutsch

Абасс... обсудите рахитекурту

18.05.24 07:37
Абасс... обсудите рахитекурту
 
alex445 патриот
Последний раз изменено 18.05.24 08:23 (alex445)

улыб

Драма в действиях - с прологом, эпилогом и интермедиями. Задача выделена жирным.


Всё просто - в классе есть свойства 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 это уже написано и работает под капотом, но для своего кода надо писать самому. Зачем так сложно, если эти значения у меня уже в готовых свойствах, как и валидация.

 

Перейти на