Обычно доменные объекты имеют свойства, которые могут быть представлены встроенным типом, но допустимые значения которых являются подмножеством значений, которые могут быть представлены этим типом.
В этих случаях значение может быть сохранено с использованием встроенного типа, но необходимо убедиться, что значения всегда проверяются в точке входа, в противном случае мы можем в итоге работать с недопустимым значением.
Одним из способов решения этой проблемы является сохранение значения как пользовательского, struct
которое имеет единственное private readonly
вспомогательное поле встроенного типа и конструктор которого проверяет предоставленное значение. Тогда мы всегда можем быть уверены в использовании только проверенных значений с использованием этого struct
типа.
Мы также можем предоставить операторы приведения к базовому встроенному типу и к нему, чтобы значения могли беспрепятственно входить и выходить как базовый тип.
Возьмем в качестве примера ситуацию, когда нам нужно представлять имя объекта домена, и допустимыми значениями являются любые строки длиной от 1 до 255 символов включительно. Мы могли бы представить это, используя следующую структуру:
public struct ValidatedName : IEquatable<ValidatedName>
{
private readonly string _value;
private ValidatedName(string name)
{
_value = name;
}
public static bool IsValid(string name)
{
return !String.IsNullOrEmpty(name) && name.Length <= 255;
}
public bool Equals(ValidatedName other)
{
return _value == other._value;
}
public override bool Equals(object obj)
{
if (obj is ValidatedName)
{
return Equals((ValidatedName)obj);
}
return false;
}
public static implicit operator string(ValidatedName x)
{
return x.ToString();
}
public static explicit operator ValidatedName(string x)
{
if (IsValid(x))
{
return new ValidatedName(x);
}
throw new InvalidCastException();
}
public static bool operator ==(ValidatedName x, ValidatedName y)
{
return x.Equals(y);
}
public static bool operator !=(ValidatedName x, ValidatedName y)
{
return !x.Equals(y);
}
public override int GetHashCode()
{
return _value.GetHashCode();
}
public override string ToString()
{
return _value;
}
}
В этом примере показано, как выполнить string
преобразование, так implicit
как оно никогда не может завершиться ошибкой, а преобразование - string
так, explicit
как это приведет к недопустимым значениям, но, конечно, оба могут быть либо либо, implicit
либо explicit
.
Также обратите внимание, что эту структуру можно инициализировать только путем приведения из string
, но можно проверить, не удастся ли такое приведение заранее, используя IsValid
static
метод.
Казалось бы, это хороший шаблон для проверки правильности значений доменов, которые могут быть представлены простыми типами, но я не вижу, чтобы они использовались часто или предлагались, и меня интересует, почему.
Итак, мой вопрос: в чем вы видите преимущества и недостатки использования этого шаблона и почему?
Если вы чувствуете, что это плохая модель, я хотел бы понять, почему, а также то, что вы считаете лучшей альтернативой.
NB. Первоначально я задавал этот вопрос о переполнении стека, но он был отложен как основанный на мнениях (иронически субъективный сам по себе) - надеюсь, он сможет добиться большего успеха здесь.
Выше приведен исходный текст, ниже еще пара мыслей, частично в ответ на ответы, полученные до того, как он приостановился:
- Один из основных моментов, сделанных ответами, касался количества кода котельной плиты, необходимого для вышеуказанного шаблона, особенно когда требуется много таких типов. Однако в защиту паттерна это может быть в значительной степени автоматизировано с использованием шаблонов, и на самом деле мне все равно это не кажется слишком плохим, но это только мое мнение.
- С концептуальной точки зрения, не кажется ли странным при работе со строго типизированным языком, таким как C #, применять только строго типизированный принцип к составным значениям, а не распространять его на значения, которые могут быть представлены экземпляром встроенный тип?