博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
.NET Core开发实战(第27课:定义Entity:区分领域模型的内在逻辑和外在行为)--学习笔记...
阅读量:4031 次
发布时间:2019-05-24

本文共 6303 字,大约阅读时间需要 21 分钟。

27 | 定义Entity:区分领域模型的内在逻辑和外在行为

上一节讲到领域模型分为两层

一层是抽象层,定义了公共的接口和类

另一层就是领域模型的定义层

先看一下抽象层的定义

1、实体接口 IEntity

namespace GeekTime.Domain{    public interface IEntity    {        object[] GetKeys();    }    public interface IEntity
: IEntity { TKey Id { get; } }}

通常情况下实体只有一个 ID,但是也不排除存在多个 ID 的情况,所以这里的接口 IEntity 定义实现为多个 ID 的情况,而 IEntity 表示实体只有一个 Id

同样看一下 Entity 的定义

public abstract class Entity : IEntitypublic abstract class Entity
: Entity, IEntity

同样地定义了一个 Entity 和 Entity,这样就可以在实体上面定义一些共享的方法,比如 ToString

public abstract class Entity : IEntity{    public abstract object[] GetKeys();    public override string ToString()    {        // 输出当前实体的名称以及它的 Id 的清单        return $"[Entity: {GetType().Name}] Keys = {string.Join(",", GetKeys())}";    }}

对于 Entity 定义了比较多的方法

public abstract class Entity
: Entity, IEntity
{ int? _requestedHashCode; public virtual TKey Id { get; protected set; } public override object[] GetKeys() { return new object[] { Id }; } ///
/// 表示对象是否相等 /// 这个方法的重载使我们可以正确的判断两个实体是否是同一个实体 /// 根据 Id 判断,如果没有 Id 的话,两个实体是不会相等的 /// ///
///
public override bool Equals(object obj) { if (obj == null || !(obj is Entity
)) return false; if (Object.ReferenceEquals(this, obj)) return true; if (this.GetType() != obj.GetType()) return false; Entity
item = (Entity
)obj; if (item.IsTransient() || this.IsTransient()) return false; else return item.Id.Equals(this.Id); } ///
/// 这个方法用来辅助对比两个对象是否相等 /// ///
public override int GetHashCode() { if (!IsTransient()) { if (!_requestedHashCode.HasValue) _requestedHashCode = this.Id.GetHashCode() ^ 31; return _requestedHashCode.Value; } else return base.GetHashCode(); } ///
/// 表示对象是否为全新创建的,未持久化的 /// ///
public bool IsTransient() { // 如果它没有 Id 就表示它没有持久化 return EqualityComparer
.Default.Equals(Id, default); } public override string ToString() { return $"[Entity: {GetType().Name}] Id = {Id}"; } ///
/// 操作符 == 重载 /// 借助上面的 Equals 方法 /// 使得可以直接用 == 判断两个领域对象是否相等 /// ///
///
///
public static bool operator ==(Entity
left, Entity
right) { if (Object.Equals(left, null)) return (Object.Equals(right, null)) ? true : false; else return left.Equals(right); } ///
/// 操作符 != 重载 /// ///
///
///
public static bool operator !=(Entity
left, Entity
right) { return !(left == right); }}

2、聚合根接口 IAggregateRoot

namespace GeekTime.Domain{    public interface IAggregateRoot    {    }}

聚合根接口实际上是一个空接口,它不实现任何的方法,它的作用是在实现仓储层的时候,让一个仓储对应一个聚合根

3、领域事件接口 IDomainEvent

namespace GeekTime.Domain{    public interface IDomainEvent : INotification    {    }}

4、域事件处理接口 IDomainEventHandler

namespace GeekTime.Domain{    public interface IDomainEventHandler
: INotificationHandler
where TDomainEvent : IDomainEvent { }}

5、还有一个领域模型里面比较关键的值对象 ValueObject

值对象的定义比较特殊,因为它是没有 Id 的,所以没有关于 Id 的定义,并且没有对值对象定义接口

重点实现了它是否相等的判断,也是重载了 Equals 这个方法和 GetHashCode 这个方法

protected static bool EqualOperator(ValueObject left, ValueObject right){    if (ReferenceEquals(left, null) ^ ReferenceEquals(right, null))    {        return false;    }    return ReferenceEquals(left, null) || left.Equals(right);}protected static bool NotEqualOperator(ValueObject left, ValueObject right){    return !(EqualOperator(left, right));}public override int GetHashCode(){    return GetAtomicValues()     .Select(x => x != null ? x.GetHashCode() : 0)     .Aggregate((x, y) => x ^ y);}

它有一个特殊的抽象方法的定义,获取它的原子值

protected abstract IEnumerable GetAtomicValues();

这个方法的作用是将值对象的字段输出出来,作为唯一标识来判断两个对象是否相等,可以看到 Equals 的定义里面也是调用了获取原子值这个方法来判断它是否相等

public override bool Equals(object obj){    if (obj == null || obj.GetType() != GetType())    {        return false;    }    ValueObject other = (ValueObject)obj;    IEnumerator thisValues = GetAtomicValues().GetEnumerator();    IEnumerator otherValues = other.GetAtomicValues().GetEnumerator();    while (thisValues.MoveNext() && otherValues.MoveNext())    {        if (ReferenceEquals(thisValues.Current, null) ^ ReferenceEquals(otherValues.Current, null))        {            return false;        }        if (thisValues.Current != null && !thisValues.Current.Equals(otherValues.Current))        {            return false;        }    }    return !thisValues.MoveNext() && !otherValues.MoveNext();}

接下来看一下定义的 Order 实体

public class Order : Entity
, IAggregateRoot{ public string UserId { get; private set; } public string UserName { get; private set; } public Address Address { get; private set; } public int ItemCount { get; private set; } protected Order() { } public Order(string userId, string userName, int itemCount, Address address) { this.UserId = userId; this.UserName = userName; this.Address = address; this.ItemCount = itemCount; this.AddDomainEvent(new OrderCreatedDomainEvent(this)); } public void ChangeAddress(Address address) { this.Address = address; }}

它首先实现了 Entity,这一个在上一节已经讲过,另外一个 Order 定义为一个聚合根,它需要实现聚合根接口 IAggregateRoot

实体中字段的 set 设置为 private,这样的好处是 Order 所有的数据的操作都应该由实体负责,而不应该被外部对象去操作,从而让领域模型符合封闭开放的原则

对于领域模型的操作,都应该是定义具有业务逻辑含义的方法来定义

比如说 ChangeAddress,就定义一个 ChangeAddress 的方法,把新的地址传进来,由领域模型负责赋值

这里面就可以添加一些地址的校验,比如新的地址是否能够与旧的地址距离太远

看一下地址的定义

public class Address : ValueObject{    public string Street { get; private set; }    public string City { get; private set; }    public string ZipCode { get; private set; }    public Address() { }    public Address(string street, string city, string zipcode)    {        Street = street;        City = city;        ZipCode = zipcode;    }    protected override IEnumerable GetAtomicValues()    {        yield return Street;        yield return City;        yield return ZipCode;    }}

只能通过构造函数给值对象赋值,这里面需要注意的是重载了获取原子值的方法,使用了 yield return

总结一下

在定义领域模型的时候,首先领域模型的字段的修改应该设置为私有的

使用构造函数来表示对象的创建,它的初始值都是由构造函数的参数来赋值的

另外需要定义有业务含义的动作来操作模型的字段

领域模型只负责自己数据的处理,领域服务或者命令负责调用领域模型的业务动作

样就可以区分领域模型的内在逻辑和外在逻辑,使代码结构更加合理

转载地址:http://hnwdi.baihongyu.com/

你可能感兴趣的文章
Android计算器实现源码分析
查看>>
Android系统构架
查看>>
Android 跨应用程序访问窗口知识点总结
查看>>
各种排序算法的分析及java实现
查看>>
SSH框架总结(框架分析+环境搭建+实例源码下载)
查看>>
js弹窗插件
查看>>
自定义 select 下拉框 多选插件
查看>>
js判断数组内是否有重复值
查看>>
js获取url链接携带的参数值
查看>>
gdb 调试core dump
查看>>
gdb debug tips
查看>>
arm linux 生成火焰图
查看>>
jtag dump内存数据
查看>>
linux和windows内存布局验证
查看>>
linux config
查看>>
linux insmod error -1 required key invalid
查看>>
linux kconfig配置
查看>>
linux不同模块completion通信
查看>>
linux printf获得时间戳
查看>>
C语言位扩展
查看>>