微型项目实践(7):数据访问的定义

简介:

上一篇中我们分析了实体类,这一篇我们来看看数据访问是怎么设计的。

system_design

从系统结构图中可以看出,到目前为止我们没有任何关于数据库访问的实现部分,而Business则仅仅是给出了IDatabase和IEntityDataAccess这两个用于定义数据访问要实现什么功能的接口。我们认为数据访问如何实现是系统的细节,而领域模型(业务逻辑)是抽象,抽象的领域模型定义、但不关心、更不依赖数据访问和数据库的设计与实现。相反,作为实现的细节,数据层根据业务逻辑的需要实现,随业务逻辑的变更而变更,这也符合DIP(接口倒置原则)。上面的系统结构图,添加数据访问后,应该是这个样子:

system_design

不仅仅是数据访问依赖于Business,UI——无论是Windows的还是Web的,也依赖于Business。在开发Web层的时候,我们还会重申这个问题。

这样的设计有很多好处:

  1. 业务逻辑直接来源于需求,由它决定系统的其它部分符合从已知到未知、从抽象到具体、从定义到实现的思维方向。需求变,则业务逻辑变;业务逻辑变,则系统皆变,这是必然的。反之数据存储及访问的方式的变更、UI显示方法的优化不会、也不应该影响业务逻辑。
  2. 数据层和表现层的可替换性。既然数据存储和UI设计的实现不会影响Business,那么这两个模块自然是可以替换的,所以当我们换用不同的数据源(Sql2000、MySql甚至XML时)或者使用不同的数据访问方法时(Linq或者自己手写SQL),只需要从新实现Business定义的接口即可。UI层类似。
  3. 可测试的业务逻辑。既然业务逻辑不依赖于数据层和表现层,那么业务逻辑层的测试就更不会依赖具体的数据库或者表现层实现方式。通常的做法就是用接口定义数据访问层需要实现的操作,测试的时候就可以用伪实现模拟不同状态的数据上下文,实现自动化测试代码。这点对于测试数据源发生错误或者数据非法的情况极为有用。

了解了各层次的依赖关系后,我们来看Business是如何定义数据访问的:

Business

数据访问使用两个接口定义,IEntityDataAccess和IDatabase,前者描述了对于一个实体的数据访问应该实现的功能,后者描述了整个数据库应该具有的功能。IEntityAccess的定义如下:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:   
   6:  namespace DongBlog.Business
   7:  {
   8:      /// <summary>
   9:      /// 实体数据访问接口
  10:      /// </summary>
  11:      /// <typeparam name="T">实体类型</typeparam>
  12:      public interface IEntityDataAccess<T> : IQueryable<T>
  13:      {
  14:          /// <summary>
  15:          /// 添加实体
  16:          /// </summary>
  17:          /// <param name="entity">实体</param>
  18:          void Add(T entity);
  19:   
  20:          /// <summary>
  21:          /// 删除实体
  22:          /// </summary>
  23:          /// <param name="entity">要删除的实体</param>
  24:          void Remove(T entity);
  25:          /// <summary>
  26:          /// 删除实体
  27:          /// </summary>
  28:          /// <typeparam name="TSubEntity">实体类型或派生类</typeparam>
  29:          /// <param name="entities">要删除的实体列表</param>
  30:          void RemoveAll<TSubEntity>(IEnumerable<TSubEntity> entities) where TSubEntity : T;
  31:   
  32:          /// <summary>
  33:          /// 取得对应的数据库
  34:          /// </summary>
  35:          IDatabase Database { get; }
  36:      }
  37:  }

接口的定义看似很简单,但是要注意到,该接口继承了IQueryable接口,所以实际上是个复杂的接口,而实现了IQueryable接口,就可使用大部分Linq查询方法。IDatabase的定义更为简单:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:   
   6:  using DongBlog.Business.Blogs;
   7:   
   8:  namespace DongBlog.Business
   9:  {
  10:      /// <summary>
  11:      /// 数据库接口
  12:      /// </summary>
  13:      public interface IDatabase
  14:      {
  15:          /// <summary>
  16:          /// 取得某一个实体的数据访问
  17:          /// </summary>
  18:          /// <typeparam name="T">实体类型</typeparam>
  19:          /// <returns>该实体的数据访问</returns>
  20:          IEntityDataAccess<T> GetDataAccess<T>() where T : class;
  21:   
  22:          /// <summary>
  23:          /// 提交数据库变更
  24:          /// </summary>
  25:          void Submit();
  26:   
  27:      }
  28:  }

只有“取得各个实体的数据访问”和“提交数据库变更”两个方法。为了方便使用,我们修改一下数据库接口的定义,如下:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:   
   6:  using DongBlog.Business.Blogs;
   7:   
   8:  namespace DongBlog.Business
   9:  {
  10:      /// <summary>
  11:      /// 数据库接口
  12:      /// </summary>
  13:      public interface IDatabase
  14:      {
  15:          /// <summary>
  16:          /// 取得某一个实体的数据访问
  17:          /// </summary>
  18:          /// <typeparam name="T">实体类型</typeparam>
  19:          /// <returns>该实体的数据访问</returns>
  20:          IEntityDataAccess<T> GetDataAccess<T>() where T : class;
  21:   
  22:          /// <summary>
  23:          /// 提交数据库变更
  24:          /// </summary>
  25:          void Submit();
  26:   
  27:          #region Blogs
  28:   
  29:          /// <summary>
  30:          /// 取得日志数据访问
  31:          /// </summary>
  32:          IEntityDataAccess<Blog> Blogs { get; }
  33:          /// <summary>
  34:          /// 取得日志分类数据访问
  35:          /// </summary>
  36:          IEntityDataAccess<Blog> BlogClasss { get; }
  37:   
  38:          #endregion
  39:   
  40:      }
  41:  }

加入了Blog和BlogClass的特有类型的数据访问,这样做的目的纯粹是为了方便。有了这两个属性,我们就可以实现前面所说的“database.Blogs.GetByID(1);”这样的调用。

下一篇文章我们将看到数据访问层究竟是如何实现的。

本文转自冬冬博客园博客,原文链接:http://www.cnblogs.com/yuandong/archive/2008/05/10/1191019.html,如需转载请自行联系原作者

相关文章
|
4月前
|
缓存 开发者 测试技术
跨平台应用开发必备秘籍:运用 Uno Platform 打造高性能与优雅设计兼备的多平台应用,全面解析从代码共享到最佳实践的每一个细节
【8月更文挑战第31天】Uno Platform 是一种强大的工具,允许开发者使用 C# 和 XAML 构建跨平台应用。本文探讨了 Uno Platform 中实现跨平台应用的最佳实践,包括代码共享、平台特定功能、性能优化及测试等方面。通过共享代码、采用 MVVM 模式、使用条件编译指令以及优化性能,开发者可以高效构建高质量应用。Uno Platform 支持多种测试方法,确保应用在各平台上的稳定性和可靠性。这使得 Uno Platform 成为个人项目和企业应用的理想选择。
73 0
|
6月前
|
存储 缓存 Linux
【实战指南】嵌入式RPC框架设计实践:六大核心类构建高效RPC框架
在先前的文章基础上,本文讨论如何通过分层封装提升一个针对嵌入式Linux的RPC框架的易用性。设计包括自动服务注册、高性能通信、泛型序列化和简洁API。框架分为6个关键类:BindingHub、SharedRingBuffer、Parcel、Binder、IBinder和BindInterface。BindingHub负责服务注册,SharedRingBuffer实现高效数据传输,Parcel处理序列化,而Binder和IBinder分别用于服务端和客户端交互。BindInterface提供简单的初始化接口,简化应用集成。测试案例展示了客户端和服务端的交互,验证了RPC功能的有效性。
425 8
|
7月前
|
算法 测试技术 数据处理
【C++ 设计思路】优化C++项目:高效解耦库接口的实战指南
【C++ 设计思路】优化C++项目:高效解耦库接口的实战指南
192 5
|
算法 5G 测试技术
5G的功能架构和灵活性 | 《5G移动无线通信技术》之十二
本节首先介绍了5G的高级要求又介绍了5G的功能架构和其灵活运用的性能。
5G的功能架构和灵活性 | 《5G移动无线通信技术》之十二
|
存储 消息中间件 SQL
云原生景观:应用程序定义和开发层解决了什么问题?如何解决的?
云原生景观:应用程序定义和开发层解决了什么问题?如何解决的?
145 0
云原生景观:应用程序定义和开发层解决了什么问题?如何解决的?
|
机器学习/深度学习 编解码 移动开发
灵活的物理层设计 | 带你读《5G系统关键技术详解》之二
本书深入介绍了 5G 无线网络的协议、网络架构和技术,包括无线接入网络、移动边 缘计算、全双工、大规模 MIMO、毫米波、NOMA、物联网、M2M 通信、D2D 通信、 移动数据分流、干扰抑制技术、无线资源管理、可见光通信和智能数据定价等关键主题。
|
存储 关系型数据库 BI