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

时间:2010-08-27 02:10:40

标签: c# oop inheritance refactoring dry

背景故事:

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

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

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

abstract public class AppCommandBase : IDisposable { }

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

abstract public class ReadCommandBase<T, ResultT> : AppCommandBase

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

abstract public class ReadItemCommandBase<T, FilterT> : ReadCommandBase<T, T> { }

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

public class MyTableReadItemCommand : ReadItemCommandBase<MyTableClass, Int?> { }

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

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

abstract public ReadListCommandBase<T> : ReadCommandBase<T, IEnumerable<T>> { }
public class MyTableReadListCommand : ReadListCommandBase<MyTableClass> { }

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

问题:

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

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

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

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

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

感谢先生们,我很感激。

5 个答案:

答案 0 :(得分:4)

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

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

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

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

赞成合成而不是继承。

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

  1. 丢弃AppCommandBase - 它不会为继承层次结构添加任何值,因为它只检查连接是否为空并打开并创建命令。
  2. 将查询构建与查询执行和结果解析分开 - 现在您可以大大简化查询执行实现,因为它是一个返回元组枚举的读操作或一个返回受影响的行数的写操作。
  3. 您的查询构建器可以全部包含在一个类中以包含分页/排序/过滤,但是围绕它们构建某种形式的有限结构可能更容易,因此您可以分离分页和排序以及过滤。如果我这样做,我不打算构建查询,我只是简单地在一个对象中写入sql,允许我传入一些参数(在c#中有效存储过程)。
  4. 所以现在你有了IDatabaseQuery / IDatabaseCommand / IResultTransformer,几乎没有继承=)

答案 1 :(得分:1)

我认为简短的回答是,在一个系统中,为了保护您的多重继承被禁止,策略/授权是 直接替代。是的,您仍然会得到一些并行结构,例如委托对象的属性。但是在语言的范围内尽量减少它。

但是让我们从简单的答案中退一步,并采取广泛的观点......

另一个重要的选择是重构更大的设计结构,这样你就可以避免这种情况,即给定的类由继承树中的多个“兄弟”或“堂兄”类的行为组合而成。更简洁地说,重构继承而不是继承。这说起来容易做起来难。它通常需要抽象出非常不同的功能。

我建议您采用这种方法的挑战是您已经在设计中做出了让步:您正在针对“项目”和“列表”案例中的不同SQL进行优化。无论如何保留这个都会妨碍你,因为你给了他们相同的账单,所以他们必须是兄弟姐妹。所以我想说,你试图摆脱设计优雅的“局部最大化”的第一步就是回滚优化并将单个项目视为真实的东西:列表的特殊情况,只有一个元件。您可以随时尝试再次为单个项目重新引入优化。但是等到你解决了目前令你烦恼的优雅问题。

但是你必须承认,除了C#代码的优雅之外的任何优化都会给C#代码的设计优雅方式带来障碍。这种权衡就像算法设计的“存储空间”共轭一样,是编程本质的基础。

答案 2 :(得分:0)

如Kirk所述,这是委托模式。当我这样做时,我通常构造一个由委托者和委托类实现的接口。这减少了感知的代码气味,至少对我而言。

答案 3 :(得分:0)

我认为简单的答案是......由于.NET不支持Multiple Inheritence,因此在创建类似类型的对象时总会有一些重复。 .NET根本没有为您提供重用某些类的工具,以促进完美DRY。

不那么简单的答案是,您可以使用代码生成工具,工具,代码dom和其他技术将所需的对象注入到所需的类中。它仍会在内存中创建重复,但它会简化源代码(代价是代码注入框架中增加的复杂性)。

这可能看起来不像其他解决方案那样令人不满意,但是如果你考虑一下,那真正支持MI的语言是在幕后做什么,挂钩你在源代码中看不到的委托系统。

问题归结为,您愿意为简化源代码付出多少努力。想一想,这是相当深刻的。

答案 4 :(得分:0)

我没有深入研究你的场景,但我对C#中的双层次问题有一些看法。要在双层次结构中共享代码,我们需要在语言中使用不同的结构:mixin,trait (pdf)C# research -pdf)或角色(如perl 6)。 C#使代码与继承共享变得非常容易(这不是代码重用的正确操作符),并且通过组合共享代码非常费力(你知道,你必须手动编写所有委托代码)。

有很多方法可以在C#中获得kind of mixin,但这并不理想。

Oxygene(download)语言(Object Pascal for .NET)也有一个有趣的interface delegation功能,可以用来为你创建所有代理代码。