DDD(三)DDD实战、贫血模型与充血模型

如果觉得样式不好:跳转即可 http://www.lifengying.site/(md文件复制过来有些样式会不一样)

贫血模型与充血模型

1、贫血模型:一个类中只有属性或者成员变量,没有方法。

2、充血模型:一个类中既有属性、成员变量,也有方法。 需求:定义一个类保存用户的用户名、密码、积分;用户必须具有用户名;为了保证安全,密码采用密码的散列值保存;用户的初始积分为10分;每次登录成功奖励5个积分,每次登录失败扣3个积分。

贫血模型:代码
 class User
 {
  public string UserName { get; set; }//用户名
  public string PasswordHash { get; set; }//密码的散列值
  public int Credit { get; set; }//积分
 }
逻辑代码
 User u1 = new User(); 
 u1.UserName = "yzk";
 u1.Credit = 10;
 u1.PasswordHash = HashHelper.Hash("123456");//计算密码的散列值
 string pwd = Console.ReadLine();
 if(HashHelper.Hash(pwd)==u1.PasswordHash)
 {
     u1.Credit += 5;//登录增加5个积分
     Console.WriteLine("登录成功");
 }
 Else
 {
     if (u1.Credit < 3)
          Console.WriteLine("积分不足,无法扣减");
     else
    {
         u1.Credit -= 3;//登录失败,则扣3个积分
         Console.WriteLine("登录成功");
    }
     Console.WriteLine("登录失败");
 }

第4行代码中,调用HashHelper.Hash方法来计算字符串的哈希值,

第5行代码中,等待用户输入一个密码,一边进行密码正确性的检查,

上面的代码可以正常的实现需求,但是有如下问题,

1、一个User对象必须具有用户名,但是在第1行中创建的User类的对象的UserName属性值是null,虽然我们很快在第2行中给它赋值了,但是如果User类使用不当,User类对象有可能处于非法状态。

2、用户的初始化积分为10,这样的领域知识是由使用者在第3行代码中设定的,而不是由User类内化的行为。

3、保存用户密码的哈希值,这样的User类内部的领域知识需要类的使用者了解,这样类的使用者才能在第4行代码和第6行代码完成设置密码及判断用户输入的密码是否正确。

4、用户的积分余额很显然不能为负数,因此我们在13~~21行代码中进行积分的扣减进行了判断,可是这样的行为应该被封装到User类中,而不应该有User类的使用者进行判断

总结

面向对象的基本特征是“封装性”:把类的内部实现细节封装起来,对外提供可供安全调用的方法,从而让类的使用者无需关心类的内部实现,一个类中核心的元素是数据和行为,数据指的是类的属性或者成员变量,而行为指的是类的方法。而我们设计的User类只包含数据,不包含行为,我们用心设计的类只能利用面向对象编程的一部分能力。

如果我们按照面向对象的原则来重新设计User类,代码如下

充血模型 :代码
 class User
 {
  public string UserName { get; init; }
  public int Credit { get; private set; }
  private string? passwordHash;
  public User(string userName)
  {
  this.UserName = userName;
  this.Credit = 10;
  }
  public void ChangePassword(string newValue)
  {
  if (newValue.Length < 6)
  {
  throw new ArgumentException("密码太短");
  }
  this.passwordHash = HashHelper.Hash(newValue);
  }
  public bool CheckPassword(string password)
  {
  string hash = HashHelper.Hash(password);
  return passwordHash == hash;
  }
  public void DeductCredits(int delta)
  {
  if (delta <= 0)
  {
  throw new ArgumentException("额度不能为负值");
  }
  this.Credit -= delta;
  }
  public void AddCredits(int delta)
  {
  this.Credit += delta;
  }
 }
逻辑代码
 User u1 = new User("yzk");
 u1.ChangePassword("123456");
 string pwd = Console.ReadLine();
 if (u1.CheckPassword(pwd))
 {
     u1.AddCredits(5);
     Console.WriteLine("登录成功");
 }
 else
 {
     Console.WriteLine("登录失败");
 }
 Console.Read();

可以看到,User类的使用者的工作量少了很多,他们需要了解的领域知识也少了很多,

有的读者可能认为,无论是贫血模型还是充血模型,只不过是逻辑代码放置的位置不一样而已,本质上没有什么区别。这样的观点是错误的。首先,从代码的角度来讲,把本应该属于User类的行为封装到User类中,这是符合单一职责原则的,当系统中其他地方需要调用User类的时候就可以复用User类中的方法了,其次,贫血模型是站在开发人员角度思考问题的,而充血模型是站在业务角度思考问题的。领域专家不明白什么是 “把用户输入的密码进行哈希运算,然后把哈希值保存起来”,但是他们明白“修改密码,检查密码成功”等充血模型反应出来的概念,因此领域模型中的所有行为都应该有业务价值,而不应该只有反映数据属性。

尽管充血模型带来的好处是明显的,但是贫血模型依旧很流行,其根本原因在于早期的很多持久性框架(比如ORM等)要求实体类的所有属性必须是可读可写,而我们可以很简单的把数据库中的表按照字段逐个映射为一个贫血模型的POCO类,这样“数据库驱动”的思维方式更简单直接,因此我们就见到“到处都是贫血模型”的情况了。值得欣慰的是,目前大部分主流持久性框架都已经支持充血模型的写法了,比如EF Core对充血模型的支持就非常好,因此我们没有理由再继续写贫血模型了。采用充血模型编写代码,我们能更好的实现DDD和模型驱动编程了。

EF Core对实体属性操作的秘密

1、Why?为EF Core实现充血模型做准备。

2、EF Core是通过实体对象的属性的get、set来进行属性的读写吗?

3、答案:基于性能和对特殊功能支持的考虑,EF Core在读写属性的时候,如果可能,它会直接跳过get、set,而直接操作真正存储属性值的成员变量。

 class Dog
 {
  public long Id { get; set; }
  private string name;
  public string Name
  {
  get
  {
  Console.WriteLine("get被调用");
  return name;
  }
  set
  {
  Console.WriteLine("set被调用");
  this.name = value;
  }
  }
 }
Program
 Dog d1 = new Dog { Name= "goofy" };
 Console.WriteLine("Dog初始化完毕");
 ctx.Dogs.Add(d1);
 ctx.SaveChanges();
 Console.WriteLine("SaveChanges完毕");
 ​
 Console.WriteLine("准备读取数据");
 Dog d2 = ctx.Dogs.First(d=>d.Name== "goofy");
 Console.WriteLine("读取数据完毕");
总结:

EF Core在读写实体对象的属性时,会查找属性对应的成员变量,如果能找到,EF Core会直接读写这个成员变量的值,而不是通过set和get代码块来读写。

修改Dog的成员变量名
 class Dog
 {
  public long Id { get; set; }
  private string xingming;
  public string Name
  {
  get
  {
  Console.WriteLine("get被调用");
  return xingming;
  }
  set
  {
  Console.WriteLine("set被调用");
  this.xingming = value;
  }
  }
 }

总结:

1、EF Core会尝试按照命名规则去直接读写属性对应的成员变量,只有无法根据命名规则找到对应成员变量的时候,EF Core才会通过属性的get、set代码块来读写属性值。

2(*)、可以在FluentAPI中通过UsePropertyAccessMode()方法来修改默认的这个行为。

EF Core中充血模型的需求:

充血模型实现的要求

一:属性是只读的或者是只能被类内部的代码修改。

二:定义有参数的构造方法。

三:有的成员变量没有对应属性,但是这些成员变量需要映射为数据表中的列,也就是我们需要把私有成员变量映射到数据表中的列。

四:有的属性是只读的,也就是它的值是从数据库中读取出来的,但是我们不能修改属性值。

五:有的属性不需要映射到数据列,仅在运行时被使用。


在EF Core中如何实现

实现“一”

属性是只读的或者是只能被类内部的代码修改。 实现:把属性的set定义为private或者init,然后通过构造方法为这些属性赋予初始值。

实现“二”

定义有参数的构造方法。 原理: EF Core中的实体类如果没有无参的构造方法,则有参的构造方法中的参数的名字必须和属性的名字一致。 实现方式1:无参构造方法定义为private。 实现方式2:实体类中不定义无参构造方法,只定义有意义的有参构造方法,但是要求构造方法中的参数的名字和属性的名字一致。

实现“三”

不属于属性的成员变量映射为数据列。 实现: builder.Property("成员变量名")

实现“四”

从数据列中读取值的只读属性。 EF Core中提供了“支持字段”(backing field)来支持这种写法:在配置实体类的代码中,使用HasField(“成员变量名”)来配置属性。

实现“五”

有的属性不需要映射到数据列,仅在运行时被使用。 实现:使用Ignore()来配置忽略这个属性。

本文内容大部分都为杨中科老师《ASP.NET Core技术内幕与项目实战》一书中内容,此文只是做学习记录,如有侵权,联系立马删除。

 

DDD(三)DDD实战、贫血模型与充血模型的更多相关文章

  1. DDD-领域驱动(二)-贫血模型与充血模型

    贫血模型 一般来说 贫血模型:**一个类中只有属性或者成员变量,没有方法 **!例如 DbFirst 从数据库同步实体过来, -- 对于一个系统刚开始的时候会觉得这时候是最舒服的,但是如果后期系统需要 ...

  2. 领域模型(domain model)&贫血模型(anaemic domain model)&充血模型(rich domain model)

    领域模型是领域内的概念类或现实世界中对象的可视化表示,又称为概念模型或分析对象模型,它专注于分析问题领域本身,发掘重要的业务领域概念,并建立业务领域概念之间的关系. 贫血模型是指使用的领域对象中只有s ...

  3. 什么是领域模型(domain model)?贫血模型(anaemic domain model) 和充血模型(rich domain model)有什么区别

    http://blog.csdn.net/helloboat/article/details/51208128 领域模型是领域内的概念类或现实世界中对象的可视化表示,又称为概念模型或分析对象模型,它专 ...

  4. 什么是领域模型(domain model)?贫血模型(anaemic domain model)和充血模型(rich domain model)有什么区别

    领域模型是领域内的概念类或现实世界中对象的可视化表示,又称为概念模型或分析对象模型,它专注于分析问题领域本身,发掘重要的业务领域概念,并建立业务领域概念之间的关系. 贫血模型是指使用的领域对象中只有s ...

  5. DDD:谈谈数据模型、领域模型、视图模型和命令模型

    背景 一个类型可以充当多个角色,这个角色可以是显式的(实现了某个接口或基类),也可以是隐式的(承担的具体职责和上下文决定),本文就讨论四个角色:数据模型.领域模型.视图模型和命令模型. 四个角色 数据 ...

  6. EF架构~充血模型设置不被持久化的属性

    回到目录 在Poco实体中,一般只有属性没有方法,这在软件设计中称为贫血模型,而在DDD领域驱动设计中,比较提倡充血模型,即你的Poco实体中,即有属性,也有操作属性的方法,注意这里说的是操作属性的方 ...

  7. 自然语言处理基础与实战(8)- 主题模型LDA理解与应用

    本文主要用于理解主题模型LDA(Latent Dirichlet Allocation)其背后的数学原理及其推导过程.本菇力求用简单的推理来论证LDA背后复杂的数学知识,苦于自身数学基础不够,因此文中 ...

  8. html学习第三天—— 第12章——css布局模型

    清楚了CSS 盒模型的基本概念. 盒模型类型, 我们就可以深入探讨网页布局的基本模型了.布局模型与盒模型一样都是 CSS 最基本. 最核心的概念. 但布局模型是建立在盒模型基础之上,又不同于我们常说的 ...

  9. ThinkPHP 学习笔记 ( 三 ) 数据库操作之数据表模型和基础模型 ( Model )

    //TP 恶补ing... 一.定义数据表模型 1.模型映射 要测试数据库是否正常连接,最直接的办法就是在当前控制器中实例化数据表,然后使用 dump 函数输出,查看数据库的链接状态.代码: publ ...

  10. Fixflow引擎解析(三)(模型) - 创建EMF模型来读写XML文件

    Fixflow引擎解析(四)(模型) - 通过EMF扩展BPMN2.0元素 Fixflow引擎解析(三)(模型) - 创建EMF模型来读写XML文件 Fixflow引擎解析(二)(模型) - BPMN ...

随机推荐

  1. VsCode轻松使用docker容器-Remote Containers

    VsCode轻松使用docker容器-Remote Containers 演示视频:BiliBili 使用docker容器过程中,最常见的操作是进入容器内查看文件.修改配置等操作 以前 使用shell ...

  2. div里元素横向排列 居中对齐

      <div>             <img src="//s.weituibao.com/1582958061265/mlogo.png" alt=&quo ...

  3. 全面jmeter逻辑控制器案例详解

    逻辑控制器作用: (1)控制测试计划或者线程组中节点的逻辑执行顺序. (2)对测试计划或者线程组中的脚本进行分组.方便jmeter统计执行结果以及脚本运行时的控制等.jmeter中逻辑控制器(Logi ...

  4. csv文件导入数据库中文乱码

    在向数据库的表中导入csv数据时,出现了中文乱码的问题,解决办法是在选择编码格式时选择10008 (MAC - Simplified Chinese GB 2312)即可

  5. WPS中VB编辑器的安装

    本来是因为要转换很多个文件的列的位置,他们格式一样,位置也一样,就是需要转换每个文件中列的位置,一个个操作又很麻烦,因此我在百度中发现了可以使用VBA进行操作,又由于excel里好像有VB编辑器,WP ...

  6. Stream流常用API

    文档 https://www.runoob.com/java/java8-streams.html JDK8 Stream API: https://docs.oracle.com/javase/8/ ...

  7. Could not match supplied host pattern, ignoring: 192.168.0.101

    [root@ansible ansible]# ansible 192.168.0.101 -m ping[WARNING]: Could not match supplied host patter ...

  8. javawebServlet

    javaweb http响应 服务器 -- 响应 -- 客户端 Accept:告诉浏览器它所支持的数据类型 Accept-Encoding:支持那种 编码格式 GBK UTF-8 GB2312 ISO ...

  9. 如何用python将txt中的package批量安装

    第一步:cd 到目标路径 第二步:新建一个requirement.txt文档,将所有要下载的包一一罗列出来(需要指定版本的话,可以用==表明) 第三步:输入命令  pip install -r req ...

  10. unity简单物理系统

    目录 技术概述 技术详述 2D物理系统组件 刚体 碰撞器 物理材质 碰撞检测函数 在主角中的使用 移动 长跳跃与短跳跃 二段跳 攀爬(蹬墙跳) 技术使用中遇到的问题和解决过程 技术概述 物理系统,就是 ...