.NET Core开发实战(第27课:定义Entity:区分领域模型的内在逻辑和外在行为)--学习笔记...
27 | 定義Entity:區(qū)分領域模型的內在邏輯和外在行為
上一節(jié)講到領域模型分為兩層
一層是抽象層,定義了公共的接口和類
另一層就是領域模型的定義層
先看一下抽象層的定義
1、實體接口 IEntity
namespace GeekTime.Domain {public interface IEntity{object[] GetKeys();}public interface IEntity<TKey> : IEntity{TKey Id { get; }} }通常情況下實體只有一個 ID,但是也不排除存在多個 ID 的情況,所以這里的接口 IEntity 定義實現為多個 ID 的情況,而 IEntity?表示實體只有一個 Id
同樣看一下 Entity 的定義
public abstract class Entity : IEntitypublic abstract class Entity<TKey> : Entity, IEntity<TKey>同樣地定義了一個 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<TKey> : Entity, IEntity<TKey> {int? _requestedHashCode;public virtual TKey Id { get; protected set; }public override object[] GetKeys(){return new object[] { Id };}/// <summary>/// 表示對象是否相等/// 這個方法的重載使我們可以正確的判斷兩個實體是否是同一個實體/// 根據 Id 判斷,如果沒有 Id 的話,兩個實體是不會相等的/// </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;if (this.GetType() != obj.GetType())return false;Entity<TKey> item = (Entity<TKey>)obj;if (item.IsTransient() || this.IsTransient())return false;elsereturn item.Id.Equals(this.Id);}/// <summary>/// 這個方法用來輔助對比兩個對象是否相等/// </summary>/// <returns></returns>public override int GetHashCode(){if (!IsTransient()){if (!_requestedHashCode.HasValue)_requestedHashCode = this.Id.GetHashCode() ^ 31;return _requestedHashCode.Value;}elsereturn base.GetHashCode();}/// <summary>/// 表示對象是否為全新創(chuàng)建的,未持久化的/// </summary>/// <returns></returns>public bool IsTransient(){// 如果它沒有 Id 就表示它沒有持久化return EqualityComparer<TKey>.Default.Equals(Id, default);}public override string ToString(){return $"[Entity: {GetType().Name}] Id = {Id}";}/// <summary>/// 操作符 == 重載/// 借助上面的 Equals 方法/// 使得可以直接用 == 判斷兩個領域對象是否相等/// </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;elsereturn 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);} }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<TDomainEvent> : INotificationHandler<TDomainEvent>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<object> GetAtomicValues();這個方法的作用是將值對象的字段輸出出來,作為唯一標識來判斷兩個對象是否相等,可以看到 Equals 的定義里面也是調用了獲取原子值這個方法來判斷它是否相等
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(); }接下來看一下定義的 Order 實體
public class Order : Entity<long>, 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,這一個在上一節(jié)已經講過,另外一個 Order 定義為一個聚合根,它需要實現聚合根接口 IAggregateRoot
實體中字段的 set 設置為 private,這樣的好處是 Order 所有的數據的操作都應該由實體負責,而不應該被外部對象去操作,從而讓領域模型符合封閉開放的原則
對于領域模型的操作,都應該是定義具有業(yè)務邏輯含義的方法來定義
比如說 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<object> GetAtomicValues(){yield return Street;yield return City;yield return ZipCode;} }只能通過構造函數給值對象賦值,這里面需要注意的是重載了獲取原子值的方法,使用了 yield return
總結一下
在定義領域模型的時候,首先領域模型的字段的修改應該設置為私有的
使用構造函數來表示對象的創(chuàng)建,它的初始值都是由構造函數的參數來賦值的
另外需要定義有業(yè)務含義的動作來操作模型的字段
領域模型只負責自己數據的處理,領域服務或者命令負責調用領域模型的業(yè)務動作
樣就可以區(qū)分領域模型的內在邏輯和外在邏輯,使代碼結構更加合理
總結
以上是生活随笔為你收集整理的.NET Core开发实战(第27课:定义Entity:区分领域模型的内在逻辑和外在行为)--学习笔记...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: BeetleX之vue-autoui自匹
- 下一篇: Abp vNext发布v2.3!