.NET重构,DRY.双重继承,数据访问和关注点分离

Ben*_*ter 5 c# oop inheritance refactoring dry

背景故事:

因此,在过去的几个晚上,我一直坚持使用我一直在玩弄的重构器上的架构问题.没什么重要的,但它一直困扰着我.它实际上是DRY中的一个练习,并试图将它带到如DAL架构完全干燥的极端.这是一个完全哲学/理论的练习.

该代码部分基于@JohnMacIntyre的一个重构,我最近说服他在http://whileicompile.wordpress.com/2010/08/24/my-clean-code-experience-no-1上写博客. /.我倾向于稍微修改代码,以便将代码更进一步 - 通常,只是为了看看我可以从概念中获得多少额外的里程......无论如何,我的理由基本上是无关紧要的.

我的部分数据访问层基于以下架构:

abstract public class AppCommandBase : IDisposable { }
Run Code Online (Sandbox Code Playgroud)

这包含基本的东西,比如在处理AppCommand之后创建命令对象和清理.我的所有命令基础对象都源于此.

abstract public class ReadCommandBase<T, ResultT> : AppCommandBase
Run Code Online (Sandbox Code Playgroud)

这包含影响所有读命令的基本内容 - 特别是在这种情况下,从表和视图中读取数据.没有编辑,没有更新,没有保存.

abstract public class ReadItemCommandBase<T, FilterT> : ReadCommandBase<T, T> { }
Run Code Online (Sandbox Code Playgroud)

这包含一些更基本的通用内容 - 比如从数据库中的表中读取单个项所需的方法定义,其中表名,键字段名和字段列表名被定义为必需的抽象属性(待定义)由派生类.

public class MyTableReadItemCommand : ReadItemCommandBase<MyTableClass, Int?> { }
Run Code Online (Sandbox Code Playgroud)

它包含定义我的表名的特定属性,表或视图中的字段列表,键字段的名称,将数据从IDataReader行解析为业务对象的方法以及启动整个过程的方法.

现在,我的ReadList也有这个结构......

abstract public ReadListCommandBase<T> : ReadCommandBase<T, IEnumerable<T>> { }
public class MyTableReadListCommand : ReadListCommandBase<MyTableClass> { }
Run Code Online (Sandbox Code Playgroud)

不同之处在于List类包含与列表生成相关的属性(即PageStart,PageSize,Sort并返回IEnumerable)与单个DataObject的返回(只需要一个标识唯一记录的过滤器).

问题:

我讨厌我的MyTableReadListCommand类中有一堆属性在我的MyTableReadItemCommand类中是相同的.我已经考虑将它们移动到一个辅助类,但是虽然可以将成员内容集中在一个地方,但我仍然在每个类中都有相同的成员,而是指向帮助类,我仍然不喜欢它.

我的第一个想法是双重继承可以很好地解决这个问题,即使我同意双重继承通常是代码味道 - 但它会非常优雅地解决这个问题.那么,鉴于.NET不支持双继承,我从哪里开始呢?

也许一个不同的重构会更合适......但是我无法绕过如何回避这个问题.

如果有人需要一个完整的代码库来查看我正在讨论的内容,我在我的DropBox上有一个原型解决方案,网址http://dl.dropbox.com/u/3029830/Prototypes/Prototype%20-%20DAL %20Refactor.zip.有问题的代码在DataAccessLayer项目中.

PS这不是正在进行的活跃项目的一部分,它更像是我自己娱乐的重构难题.

在此先感谢大家,我很感激.

Nea*_*eal 4

将结果处理与数据检索分开。在 ReadCommandBase 中,您的继承层次结构已经足够深了。

定义一个接口IDatabaseResultParser。实现 ItemDatabaseResultParser 和 ListDatabaseResultParser,两者都具有 ReadCommandBase 类型的构造函数参数(也可能将其转换为接口)。

当您调用 IDatabaseResultParser.Value() 时,它会执行命令、解析结果并返回 T 类型的结果。

您的命令侧重于从数据库中检索数据并将它们作为某种描述的元组返回(实际元组或数组的数组等),您的解析器侧重于将元组转换为您需要的任何类型的对象。请参阅 NHibernates IResultTransformer 了解其工作原理(它可能也是比 IDatabaseResultParser 更好的名称)。

优先考虑组合而不是继承。

看过样本后我会更进一步......

  1. 扔掉 AppCommandBase - 它不会为您的继承层次结构添加任何值,因为它所做的只是检查连接是否不为空并打开并创建命令。
  2. 将查询构建与查询执行和结果解析分开 - 现在您可以极大地简化查询执行实现,因为它要么是返回元组枚举的读取操作,要么是返回受影响的行数的写入操作。
  3. 您的查询构建器可以全部包含在一个类中以包括分页/排序/过滤,但是围绕这些构建某种形式的有限结构可能更容易,以便您可以单独分页、排序和过滤。如果我这样做,我就不会费心构建查询,我只需将 sql 写入一个对象内,该对象允许我传递一些参数(c# 中的有效存储过程)。

所以现在你有 IDatabaseQuery / IDatabaseCommand / IResultTransformer 并且几乎没有继承 =)

  • “优先考虑组合而不是继承。” &lt;-- 一个总是被教导的教训,很容易被忘记,直到忘记它会在我们背后咬伤。很好的答案。 (3认同)