.NET Core开发实战(第27课:定义Entity:区分领域模型的内在逻辑和外在行为)--学习笔记...

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

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

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

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

先看一下抽象层的定义

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>/// 表示对象是否为全新创建的,未持久化的/// </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,这一个在上一节已经讲过,另外一个 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<object> GetAtomicValues(){yield return Street;yield return City;yield return ZipCode;}
}

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

总结一下

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

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/311013.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Abp vNext发布v2.3!

在全球范围内病毒笼罩的日子里,我们发布了ABP框架v2.3, 这篇文章将说明本次发布新增内容和过去的两周我们做了什么.关于新冠病毒和我们的团队关于冠状病毒的状况我们很难过.在Volosoft的团队,我们有不同国家的远程工作者在自己家里工作.从上周开始,我们已经完全开始在家远程工作…

ASP.NET Core 中间件分类

ASP.NET Core 中间件的配置方法可以分为以上三种&#xff0c;对应的Helper方法分别是&#xff1a;Run(), Use(), Map()。Run()&#xff0c;使用Run调用中间件的时候&#xff0c;会直接返回一个响应&#xff0c;所以后续的中间件将不会被执行了。Use()&#xff0c;它会对请求做一…

redis持久化到mysql的方案_redis进阶: 数据持久化

redis是内存数据库&#xff0c;即数据库状态都是存储于内存中&#xff0c;因此&#xff0c;当服务器重启或者断开后&#xff0c;数据便会丢失&#xff1b;为了解决数据丢失问题&#xff0c;便需要将数据从内存保持到磁盘中&#xff0c;这就是redis的数据持久化目前&#xff0c;…

如何创建一个自定义的`ErrorHandlerMiddleware`方法

在本文中&#xff0c;我将讲解如何通过自定义ExceptionHandlerMiddleware&#xff0c;以便在中间件管道中发生错误时创建自定义响应&#xff0c;而不是提供一个“重新执行”管道的路径。作者&#xff1a;依乐祝译文&#xff1a;https://www.cnblogs.com/yilezhu/p/12497937.htm…

mysql or中有空查询慢_MySQL 慢查询日志

1.定义2.相关参数2.开启3.原因4.慢查询日志工具mysqldumpslow1.定义作用:用来记录在MySQL中响应时间超过阀值的语句。2.相关参数mysql> show variables like %slow_query%;------------------------------------------------------------| Variable_name | Value …

从业务需求抽象成模型解决方案

从业务需求调研&#xff0c;通过抽象转换成模型技术方案&#xff0c;本文将对这个过程做个拆解&#xff0c;供大家参考。以下我所说的可能都是错的&#xff0c;只是一家之见&#xff0c;欢迎大家在留言区多提意见和看法&#xff0c;互相共勉。一、订单对象-信息需求公司的运营都…

《C++ Primer》7.1.4节练习

练习7.11: #include <iostream> #include <cstring> using namespace std;class Sales_data {public:Sales_data() default;Sales_data(const std::string &book): bookNo(book) {}Sales_data(const std::string &book, const unsigned num, const doubl…

DotNetCore Web应用程序中的Session管理

原文来自互联网&#xff0c;由长沙DotNET技术社区编译。如译文侵犯您的署名权或版权&#xff0c;请联系小编&#xff0c;小编将在24小时内删除。限于译者的能力有限&#xff0c;个别语句翻译略显生硬&#xff0c;还请见谅。作者简介&#xff1a;Jon&#xff08;Jonathan&#x…

css3是什么 ptml_CSS3

CSS3HTMLCSSJavaScript结构表项交互如何学习&#xff1f;CSS是什么CSS怎么用(快速入门)CSS选择器(重点难点)美化网页(文字、阴影、超链接、列表、渐变...)盒子模型浮动定位网页动画(特效效果)1、初识CSS1.1、什么是CSSCascading Style Sheet(层叠样式表)CSS&#xff1a;表现(美…

python docx runs_别再问我Python怎么操作Word了!

安装docx是一个非标准库&#xff0c;需要在命令行(终端)中使用pip即可安装pip install python-docx一定要注意&#xff0c;安装的时候是python-docx而实际调用时均为docx!前置知识Word中一般可以结构化成三个部分&#xff1a;文档Document段落Paragraph文字块Run也就是Document…

【要闻】如何基于K8s管理1600个微服务?某数字化银行秘诀公开

Cloud Foundry Foundation宣布KubeCF为新孵化项目Cloud Foundry Foundation是开放源代码项目的聚集地&#xff0c;简化了开发人员的体验&#xff0c;近日其宣布&#xff0c;KubeCF已成为该基金会的孵化项目&#xff0c;并已发布版本1.0.1。KubeCF是Cloud Foundry应用程序运行时…

如何用 Blazor 实现 Ant Design 组件库?

本文主要分享我创建 Ant Design of Blazor 项目的心路历程&#xff0c;已经文末有一个 Blazor 线上分享预告。Blazor WebAssembly 来了&#xff01;Blazor 这个新推出的前端 Web 框架&#xff0c;想必是去年 .NET Core 3.0 发布时才进入 .NET 开发者的视线的。但其实&#xff0…

.NET Core开发实战(第28课:工作单元模式(UnitOfWork):管理好你的事务)--学习笔记...

28 | 工作单元模式&#xff08;UnitOfWork&#xff09;&#xff1a;管理好你的事务工作单元模式有如下几个特性&#xff1a;1、使用同一上下文2、跟踪实体的状态3、保障事务一致性我们对实体的操作&#xff0c;最终的状态都是应该如实保存到我们的存储中&#xff0c;进行持久化…

《C++ Primer》7.3.2节练习

练习7.27: #include <iostream> #include <cstring> using namespace std;class Screen {private:unsigned height 0, width 0;unsigned cursor 0;string contents;public:Screen() default;Screen(unsigned ht, unsigned wd): height(ht), width(wd), conten…

【实战 Ids4】║ 控制台密码模式搭配Ocelot网关

&#xff08;此岁只能云赏樱了&#xff09;书接上文&#xff0c;这些天一直在研究IdentityServer4&#xff08;下文简称Ids4&#xff09;框架&#xff0c;发现有很多有意思&#xff0c;或者说比我想象中的知识点&#xff0c;可扩展的多&#xff0c;所以比较开心能钻研进去&…

《C++ Primer》7.3.3节练习

练习7.31: 满足题意的程序如下所示&#xff1a; class X;//声明类型X class Y//定义类型Y {X* x; }; class X//定义类型X {Y y; };类X的声明称为前向声明&#xff0c;它向程序中引入了名字X并且指明X是一种类类型。对于类型X来说&#xff0c;此时我们已知它是一个类类型&#…

.NET Core 如何生成信用卡卡号

点击上方蓝字关注“汪宇杰博客”导语上个月我写了《.NET Core 如何验证信用卡卡号》&#xff0c;不少朋友表示挺有兴趣。在金融科技行业的实际工作中&#xff0c;通常还需要生成信用卡卡号用来测试&#xff0c;今天我就来教大家如何生成信用卡卡号。上回的改进上篇文章写完后&a…

python怎么爬虎牙_使用python爬虫框架scrapy抓取虎牙主播数据

前言本文利用python的scrapy框架对虎牙web端的主播、主播订阅数、主播当前观看人数等基本数据进行抓取&#xff0c;并将抓取到的数据以csv格数输出&#xff0c;以及存储到mongodb中思路观察虎牙网站后确认所有频道url都在www.huya.com/g中的&#xff0c;而主播房间数据则是ajax…

《C++ Primer》7.3.4节练习

练习7.32: 要想让clear函数作为Screen的友元&#xff0c;只需要在Screen类中做出友元声明即可。本题的真正关键之处是程序的组织结构&#xff0c;我们必须首先定义Window_mgr类&#xff0c;其中声明clear函数&#xff0c;但是不能定义它&#xff1b;接下来定义Screen类&#xf…

.NET Core开发实战(第29课:定义仓储:使用EF Core实现仓储层)--学习笔记

29 | 定义仓储&#xff1a;使用EF Core实现仓储层首先定义仓储层的接口&#xff0c;以及仓储层实现的基类&#xff0c;抽象类仓储层的接口namespace GeekTime.Infrastructure.Core {/// <summary>/// 包含普通实体的仓储/// 约束 TEntity 必须是继承 Entity 的基类&#…