虽然最初的问题集中在映射器的问题上,但我想将重点转移到改进设计选择上。
借用 DDD(领域驱动设计)的概念,考虑使用值对象来表示视图模型的 BirthDate 值。
我创建了一个基础对象来覆盖重复的功能
public abstract class ValueObject<T> where T : ValueObject<T> {
public override bool Equals(object obj) {
var valueObject = obj as T;
if (ReferenceEquals(valueObject, null))
return false;
return EqualsCore(valueObject);
}
private bool EqualsCore(T other) {
return GetEqualityComponents().SequenceEqual(other.GetEqualityComponents());
}
protected abstract IEnumerable<object> GetEqualityComponents();
public override int GetHashCode() {
return GetEqualityComponents()
.Aggregate(1, (current, obj) => current * 23 + (obj == null ? 0 : obj.GetHashCode()));
}
public static bool operator ==(ValueObject<T> left, ValueObject<T> right) {
if (ReferenceEquals(left, null) && ReferenceEquals(right, null))
return true;
if (ReferenceEquals(left, null) || ReferenceEquals(right, null))
return false;
return left.Equals(right);
}
public static bool operator !=(ValueObject<T> left, ValueObject<T> right) {
return !(left == right);
}
}
然后按照所需的模式创建一个BirthDate 值对象
public class BirthDate : ValueObject<BirthDate>, IEquatable<BirthDate> {
DateTime value;
public BirthDate(DateTime value) {
this.value = value;
}
public static implicit operator BirthDate(DateTime dt) {
return new BirthDate(dt);
}
public static implicit operator BirthDate(string src) {
return DateTime.ParseExact(src, AppConstants.DefaultDateFormat, CultureInfo.InvariantCulture);
}
public static implicit operator DateTime(BirthDate dt) {
return dt.value;
}
public static implicit operator String(BirthDate dt) {
return dt.ToString();
}
public override string ToString() {
return value.ToString(AppConstants.DefaultDateFormat);
}
public bool Equals(BirthDate other) {
return DateTime.Equals(this.value, other.value);
}
protected override IEnumerable<object> GetEqualityComponents() {
yield return value;
}
}
注意允许在所需类型之间转换值对象的隐式运算符。主要是DateTime和格式化string
视图模型将值对象作为属性
public class PersonViewModel {
//...
public BirthDate BirthDate { get; set; } <-- note the return type
//...
}
以下单元测试被用来练习不同值对象和所需类型之间的转换,甚至通过映射器。
[TestClass]
public class BirthDateValueObjectTests {
public class Person {
public DateTime BirthDate { get; set; }
}
public class PersonViewModel {
public BirthDate BirthDate { get; set; }
}
string date = "1981-05-14";
static BirthDateValueObjectTests() {
AutoMapper.Mapper.Initialize(_ => {
});
}
[TestMethod]
public void BirthDate_Should_Implcit_Convert_From_DateTimeProperty() {
//Arrange
var birthDate = DateTime.Parse(date);
var person = new Person {
BirthDate = birthDate
};
var expected = new BirthDate(birthDate);
var mapper = AutoMapper.Mapper.Instance;
//Act
var actual = mapper.Map<PersonViewModel>(person);
//Assert
actual.BirthDate
.Should().NotBeNull()
.And.Be(expected);
}
[TestMethod]
public void BirthDate_Should_Implcit_Convert_To_DateTimeProperty() {
//Arrange
var birthDate = DateTime.Parse(date);
var person = new PersonViewModel {
BirthDate = new BirthDate(birthDate)
};
var expected = birthDate;
var mapper = AutoMapper.Mapper.Instance;
//Act
var actual = mapper.Map<Person>(person);
//Assert
actual.BirthDate
.Should().Be(expected);
}
[TestMethod]
public void BirthDate_Should_Implicitly_ConvertTo_DateTime() {
var expected = DateTime.Parse(date);
var birthDate = new BirthDate(expected);
DateTime actual = birthDate;
actual.Should().Be(expected);
}
[TestMethod]
public void BirthDate_Should_Implicitly_ConvertFrom_DateTime() {
var birthDate = DateTime.Parse(date);
var expected = new BirthDate(birthDate);
BirthDate actual = birthDate;
actual.Should().Be(expected);
}
[TestMethod]
public void BirthDate_Should_Implicitly_ConvertTo_String() {
var expected = DateTime.Parse(date);
var birthDate = new BirthDate(expected);
string actual = birthDate;
actual.Should().Be(date);
}
[TestMethod]
public void BirthDate_Should_Implicitly_ConvertFrom_String() {
var birthDate = DateTime.Parse(date);
var expected = new BirthDate(birthDate);
BirthDate actual = date;
actual.Should().Be(expected);
}
}
所以现在映射器可以通过直接映射进行转换,并且视图模型的BirthDate 属性可以在具有所需格式的视图中使用,因为在映射器配置中完成的实现问题被封装在具有作为出生日期的唯一责任。
虽然此答案专门针对出生日期,但值对象可以重命名并更普遍地用于其他日期时间属性,以减少重复代码 (DRY)。