• 重新整理 .net core 实践篇—————Entity的定义[二十五]


    前言

    简单介绍一下实体模型的设计。

    正文

    前文提及了我们的应用分为:

    1. 共享层

    2. 基础设施层

    3. 领域层

    4. 应用层

    今天来介绍领域模型层。

    前文提及到领域模型在共享层有一个领域模型抽象类库。

    里面有这些类:

    先分别介绍一下这些类是做什么的。

    IEntity 类,是我们实体的接口:

    /// <summary>
    /// 实体接口(包含多个主键的实体接口)
    /// </summary>
    public interface IEntity
    {
    	object[] GetKeys();
    }
    
    /// <summary>
    /// 实体接口(包含唯一主键Id的实体接口)
    /// </summary>
    /// <typeparam name="TKey">主键ID类型</typeparam>
    public interface IEntity<TKey> : IEntity
    {
    	TKey Id { get; }
    }
    

    实现抽象类Entity:

    /// <summary>
    /// 实体抽象类(包含多个主键的实体接口)
    /// </summary>
    public abstract class Entity : IEntity
    {
    	public abstract object[] GetKeys();
    
    	public override string ToString()
    	{
    		return $"[Entity:{GetType().Name}] Keys = {string.Join(",", GetKeys())}";
    	}
    
    	#region 领域事件定义处理 DomainEvents
    
    	/// <summary>
    	/// 领域事件集合
    	/// </summary>
    	private List<IDomainEvent> _domainEvents;
    
    	/// <summary>
    	/// 获取当前实体对象领域事件集合(只读)
    	/// </summary>
    	public IReadOnlyCollection<IDomainEvent> DomainEvents => _domainEvents?.AsReadOnly();
    
    	/// <summary>
    	/// 添加领域事件至当前实体对象领域事件结合中
    	/// </summary>
    	/// <param name="eventItem"></param>
    	public void AddDomainEvent(IDomainEvent eventItem)
    	{
    		_domainEvents = _domainEvents ?? new List<IDomainEvent>();
    		_domainEvents.Add(eventItem);
    	}
    
    	/// <summary>
    	/// 移除指定领域事件
    	/// </summary>
    	/// <param name="eventItem"></param>
    	public void RemoveDomainEvent(IDomainEvent eventItem)
    	{
    		_domainEvents?.Remove(eventItem);
    	}
    
    	/// <summary>
    	/// 清空所有领域事件
    	/// </summary>
    	public void ClearDomainEvents()
    	{
    		_domainEvents?.Clear();
    	}
    
    	#endregion
    }
    
    /// <summary>
    /// 实体抽象类(包含唯一主键Id的实体接口)
    /// </summary>
    /// <typeparam name="TKey">主键ID类型</typeparam>
    public abstract class Entity<TKey> : Entity, IEntity<TKey>
    {
    	int? _requestedHasCode;
    	public virtual TKey Id { get; protected set; }
    	public override object[] GetKeys()
    	{
    		return new object[] { Id };
    	}
    
    	/// <summary>
    	/// 对象是否想等
    	/// </summary>
    	/// <param name="obj"></param>
    	/// <returns></returns>
    	public override bool Equals(object obj)
    	{
    		if (obj == null || !(obj is Entity<TKey>))
    		{
    			return false;
    		}
    
    		if (Object.ReferenceEquals(this, obj))
    		{
    			return true;
    		}
    
    		Entity<TKey> item = (Entity<TKey>)obj;
    
    		if (item.IsTransient() || this.IsTransient())
    		{
    			return false;
    		}
    		else
    		{
    			return item.Id.Equals(this.Id);
    		}
    	}
    
    	public override int GetHashCode()
    	{
    		if (!IsTransient())
    		{
    			if (!_requestedHasCode.HasValue)
    			{
    				_requestedHasCode = this.Id.GetHashCode() ^ 31; // TODO
    			}
    			return _requestedHasCode.Value;
    		}
    		else
    		{
    			return base.GetHashCode();
    		}
    	}
    
    	/// <summary>
    	/// 对象是否为全新创建的,未持久化的
    	/// </summary>
    	/// <returns></returns>
    	public bool IsTransient()
    	{
    		return EqualityComparer<TKey>.Default.Equals(Id, default);
    	}
    	public override string ToString()
    	{
    		return $"[Entity:{GetType().Name}] Id = {Id}";
    	}
    
    	/// <summary>
    	/// == 操作符重载
    	/// </summary>
    	/// <param name="left"></param>
    	/// <param name="right"></param>
    	/// <returns></returns>
    	public static bool operator ==(Entity<TKey> left,Entity<TKey> right)
    	{
    		if (Object.Equals(left,null))
    		{
    			return (Object.Equals(right, null)) ? true : false;
    		}
    		else
    		{
    			return left.Equals(right);
    		}
    	}
    
    	/// <summary>
    	/// != 操作符重载
    	/// </summary>
    	/// <param name="left"></param>
    	/// <param name="right"></param>
    	/// <returns></returns>
    	public static bool operator !=(Entity<TKey> left, Entity<TKey> right)
    	{
    		return !(left == right);
    	}
    }
    

    聚合根接口IAggregateRoot.cs:

    /// <summary>
    /// 聚合根接口
    /// 作用是我们在实现仓储层的时候,让我们的一个仓储对应一个聚合根
    /// </summary>
    public interface IAggregateRoot
    {
    }
    

    领域事件IDomainEvent :

    /// <summary>
    /// 领域事件接口
    /// 用来标记我们某一个对象是否是领域事件
    /// </summary>
    public interface IDomainEvent : INotification
    {
    }
    

    领域事件的处理接口IDomainEventHandler:

    /// <summary>
    /// 领域事件处理器接口
    /// </summary>
    public interface IDomainEventHandler<TDomainEvent> : INotificationHandler<TDomainEvent> where TDomainEvent : IDomainEvent
    {
    	//这里我们使用了INotificationHandler的Handle方法来作为处理方法的定义,所以无需重新定义
    	//Task Handle(TDomainEvent domainEvent, CancellationToken cancellationToken);
    }
    

    值对象 ValueObject:

    /// <summary>
    /// 值对象
    /// TODO 领域驱动中比较关键的类
    /// </summary>
    public abstract class ValueObject
    {
    	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 NotEqualQperator(ValueObject left, ValueObject right)
    	{
    		return !(EqualOperator(left, right));
    	}
    
    	/// <summary>
    	/// 获取值对象原子值(字段值)
    	/// 将我们值对象的字段输出出来,作为唯一标识来判断我们两个对象是否想等
    	/// </summary>
    	/// <returns></returns>
    	protected abstract IEnumerable<object> GetAtomicValues();
    
    	public override bool Equals(object obj)
    	{
    		if (obj == null || obj.GetType() != GetType())
    		{
    			return false;
    		}
    		ValueObject other = (ValueObject)obj;
    		IEnumerator<object> thisValues = GetAtomicValues().GetEnumerator();
    		IEnumerator<object> 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();
    	}
    
    	public override int GetHashCode()
    	{
    		return GetAtomicValues()
    				.Select(x => x != null ? x.GetHashCode() : 0)
    				.Aggregate((x, y) => x ^ y);
    	}
    }
    

    那么来看一下领域模型的具体实现:

    先来看一下Aggregate 的具体实现:

    /// <summary>
    /// 订单实体
    /// </summary>
    public class Order : Entity<long>, IAggregateRoot
    {
    	// 实体内字段的 set 方法都是 private 的
    	// 实体类型相关的数据操作,都应该是由我们实体来负责,而不是被外部的对象去操作
    	// 这样的好处是让我们的领域模型符合封闭开放的原则
    
    	public string UserId { get; private set; }
    	public string UserName { get; private set; }
    	public Address Address { get; private set; }
    	public int ItemCount { get; set; }
    
    	protected Order()
    	{
    	}
    
    	public Order(string userId, string userName, int itemCount, Address address)
    	{
    		this.UserId = userId;
    		this.UserName = userName;
    		this.ItemCount = itemCount;
    		this.Address = address;
    
    		// 构造新的Order对象的时候,添加一个创建Order领域事件
    		this.AddDomainEvent(new OrderCreatedDomainEvent(this));
    	}
    
    	/// <summary>
    	/// 修改收货地址
    	/// </summary>
    	/// <param name="address"></param>
    	public void ChangeAddress(Address address)
    	{
    		this.Address = address;
    
    		// 同样的,在修改地址操作时,也该定义一个类似的修改地址领域事件
    		//this.AddDomainEvent(new OrderAddressChangedDomainEvent(this));
    	}
    }
    

    这里面实现了Entity,同时实现了IAggregateRoot,这个接口里面没有任何东西,表示这是定义接口,表示将这个Order 定义为一个聚合根。

    看一下Address:

    /// <summary>
    /// 地址实体
    /// 定义为值对象
    /// </summary>
    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)
    	{
    		this.Street = street;
    		this.City = city;
    		this.ZipCode = zipCode;
    	}
    
    	/// <summary>
    	/// 重载获取原子值的方法
    	/// 这里特殊的是,我们使用了 yield return 的方式
    	/// </summary>
    	/// <returns></returns>
    	protected override IEnumerable<object> GetAtomicValues()
    	{
    		yield return Street;
    		yield return City;
    		yield return ZipCode;
    	}
    }
    

    将这个Address 定义为值对象。

    梳理

    这里如果不了解领域设计,会有点蒙。

    那么这里只需要知道这里Order 定义为了aggregateRoot,也就是聚合根。

    把Address 定义为了值对象即可。随着后面系列的他们之间的调用会越来越清晰的。

    总结

    1. 将领域模型字段的修改设置为私有的。

    2. 使用构造函数表示对象的创建

    3. 使用具有业务的动作来操作模型字段

    4. 领域模型复制堆自己数据的处理

    5. 领域模型负责对自己数据的处理

    6. 领域服务或命令处理者扶着调用领域模型业务动作

    下一节 领域模型之工作单元模式

  • 相关阅读:
    第二章 1.绘制文本
    在Windows Mobile 5中使用DirectShow控制摄像头转
    写会议纪要也是需要水平滴
    注重实效的程序员(The Pragmatic Programmer)[转载]
    项目经理面试指南(上)[转载]
    上海西门子培训序
    周末桂林游
    我们应该如何面试程序员/技术人员?
    Erlang Code Auto Reloader
    新车落地几种常见的配件加装建议
  • 原文地址:https://www.cnblogs.com/aoximin/p/14904996.html
Copyright © 2020-2023  润新知