Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
Customer и методами-сеттерами, имхо, высосан из пальца. Почему бы просто не разместить валидацию в коде сеттера свойства?Properties have many uses: they can validate data before allowing a change; they can transparently expose data on a class where that data is actually retrieved from some other source, such as a database; they can take an action when data is changed, such as raising an event, or changing the value of other fields.
Вопрос в целесообразности использования такого подхода при каких-то цепочках валидации, зависимости одной проперти от другой и так далее.
ChangeSmth делать, а разносить присвоение и валидацию. А если валидация свойства атомарна (не зависит от других свойств), то ее целесообразно делать в сеттере.class Range
{
private int _min;
private int _max;
public int Min
{
get { return _min; }
set { if(value > Max) throw new InvalidOperationException(); _min = value; }
}
public int Max
{
get { return _max; }
set { if (value < Min) throw new InvalidOperationException(); _max = value; }
}
}
Обратите вниманите, что конструктор класса Email закрыт, так что единственный способ создать его экземпляр — использовать статический метод Create, который проводит всю необходимую валидацию.
[HttpPost]
public ActionResult CreateCustomer(CustomerInfo customerInfo)
{
Result<Email> emailResult = Email.Create(customerInfo.Email);
Result<CustomerName> nameResult = CustomerName.Create(customerInfo.Name);
if (emailResult.Failure)
ModelState.AddModelError(“Email”, emailResult.Error);
if (nameResult.Failure)
ModelState.AddModelError(“Name”, nameResult.Error);
...
}
Более строгая система типов. Компилятор работает на нас с удвоенной силой: теперь невозможно ошибочно присвоить свойству типа Email объект типа CustomerName, такой код не будет скомпилирован.
City и Country) такой проверки нет, и теперь программисту нужно помнить про два разных механизма. Не ужас, но неудобно.Но при этом для других свойств (скажем, City и Country) такой проверки нет, и теперь программисту нужно помнить про два разных механизма. Не ужас, но неудобно.
Если у City и Country есть какие-то более-менее сложные инварианты, то их тоже стоит обернуть в классы-обертки.
Первый — вместе с ужесточением инвариантов писать скрипты для миграции данных в БД, чтобы они соответствовали новым инвариантам.
Второй — создавать отдельный класс, к примеру EmailStrong для хранения имейлов, инварианты в которых были ужесточены и не давать присваивать кастомерам объекты старого Email, только нового.
особенно учитывая, что где-то присваивать можно (мало ли, мы данные из системы в систему гоним). Опять-таки, в систему типов этот запрет присвоения уже не обернешь, снова боль.
лучше все-таки использовать сеттеры, а не методы
Т.е. какие-то методы будут работать только с новыми имейлами, для других — мы оставляем как было.
Здесь как раз пригодится сделать небольшие методы-сеттеры для обозначения новых инвариантов класса.
А что мешает использовать методы со старыми сигнатурами?
… и теперь сломался ранее работавший маппинг.
Ну мы же хотим чтобы кастомерам можно было присваивать только имейлы с новыми инвариантами? Если так, то можно на уровне компилятора обозначить это изменение.
Маппинг куда?
как мы запретим программисту использовать эти механизмы?Запретить не запретим, но если добавить запашку — Deprecated?
Result<Email>Не является ли это анти-паттерном в C#? Такой тип идеален в Rust, там нет исключений, но в C# используются две модели — исключения и bool TryXXX(..., out yyy).bool TryXXX(..., out yyy) возвращает слишком мало информации в случае ошибки. [Required(ErrorMessage = “E-mail is required”)]
[RegularExpression(@”^([\w\.\-]+)@([\w\-]+)((\.(\w){2,3})+)$”, ErrorMessage = “Invalid e-mail address”)]
[StringLength(100, ErrorMessage = “E-mail is too long”)]
public string Email { get; set; }public void UpdatePrimaryEmail(Email email)
{
_primaryEmail = email;
}
public void UpdateSecondaryEmail(Email email)
{
_seconaryEmail = email;
}
public void ClearSeconaryEmail()
{
_seconadryEmail = null;
}
Functional C#: Primitive obsession (одержимость примитивами)