繁体   English   中英

要避免的类(代码完成)

[英]Classes to avoid (code complete)

我对代码完整书中的段落感到有些困惑。

在“要避免的类”一节中,它写着:

“避免使用动词命名的类只有行为但没有数据的类通常不是一个类。考虑将类似DatabaseInitialization()或StringBuilder()的类转换为其他类的例程”

我的代码主要由没有数据的动词类组成​​。 有发票阅读器,价格计算器,消息构建器等。我这样做是为了将每个类集中到一个任务。 然后我将依赖关系添加到其他类以获取其他功能。

如果我正确理解了段落,我应该使用代码

class Webservice : IInvoiceReader, IArticleReader {
    public IList<Invoice> GetInvoices();
    public IList<Article> GetArticles();
}

而不是

class InvoiceReader : IInvoiceReader {
    public InvoiceReader(IDataProvider dataProvider);
    public IList<Invoice> GetInvoices();
}

class ArticleReader : IArticleReader {
    public ArticleReader(IDataProvider dataProvider);
    public IList<Article> GetArticles();
}

编辑感谢所有回复。

我的结论是,我目前的代码比OO更多SRP,但它也受到“贫血领域模型”的影响。

我相信这些见解将来会帮助我。

InvoiceReader,PriceCalculator,MessageBuilder,ArticleReader,InvoiceReader等类名实际上不是动词名称。 它们实际上是“名词代理 - 名词”类名。 代理商名词

动词类名称将类似于Validate,Operate,Manage等。显然,这些更好地用作方法,并且作为类名称会非常笨拙。

“名词代理 - 名词”类名的最大问题在于它们对类的实际作用(例如UserManager,DataProcessor等)几乎没有意义。 结果他们更容易臃肿并失去内部凝聚力。 (参见单一责任原则 )。

因此,具有IInvoiceReader和IArticleReader接口的WebService类可能是更清晰,更有意义的OO设计。

这为您提供了简单明了的名词类名“WebService”,以及“名词代理 - 名词”接口名称,它清楚地宣传了WebService类可以为调用者做些什么。

您可能还可以通过为另一个名词添加前缀来为实际类赋予更多含义,例如PaymentWebService。

但是,在更具体地描述类可以为调用者做什么时,接口总是比单个类名更好。 随着类变得越来越复杂,新接口也可以添加有意义的名称。

我个人无视这个“规则”。 .NET框架本身就充满了“动词”类: TextReaderBinaryWriterXmlSerializerCodeGeneratorStringEnumeratorHttpListenerTraceListenerConfigurationManagerTypeConverterRoleProvider ......如果你认为框架设计不当,然后通过各种手段,不要使用这些名称。

史蒂夫的意图是可以理解的。 如果你发现自己只是为了执行一项特定的任务而创建了几十个类,这可能是一个贫血领域模型的标志, 应该能够自己做这些事情的对象不是。 但在某些时候,你必须在“纯粹的”OOP和SRP之间做出选择。

我的建议是这样的:如果你发现自己创建一个“动词”类作用于单个“名词”类,请诚实地思考“名词”类是否可以自己执行动作。 但是,不要开始创造God Objects,或者为了避免使用动词类而提出毫无意义/误导性的名称。

不要盲目听从任何建议。 这些只是指导方针。

也就是说,只要名词模拟逻辑对象, 名词就会成为非常好的类名 由于“Person”类是所有“Person”对象的蓝图,因此将其称为“Person”非常方便,因为它允许您这样推理:“我将根据用户的输入创建Person,但首先我需要验证它......“

请注意使用“避免”一词。 如果你曾经使用它,它不会消除,根除或在地狱中燃烧。

作者的意思是,如果你发现自己有一堆所有以动词命名的类,你所要做的就是静态创建类,调用一个函数而忘记它们,这可能是你分开一点点的标志有点太多关注。

但是,有些情况下,创建实现操作的类是一件好事,例如当您对同一操作有不同的策略时。 一个很好的例子是IComparer <>。 它只是比较两件事,但有几种比较方法。

正如作者所建议的那样,在这些情况下,一个好方法是创建一个接口并实现它。 IComparer <>再次浮现在脑海中。

另一种常见情况是当操作处于繁重状态时,例如加载文件。 将状态封装在类中可能是合理的。

本书的基本内容是OO设计是关于提取对象(名词)和识别在这些对象之间和之间发生的操作(动词)。

名词成为对象,动词成为对这些对象进行操作的方法。

这个想法是

越接近程序模拟现实世界的问题,程序就越好。

实际上,对象的有用之处在于它可以表示特定的状态。 然后,您可以拥有此类的几个不同实例,每个实例都持有不同的状态来表示问题的某些方面。

在InvoiceReader类的情况下

  • 你只会创建一个实例
  • 它所代表的唯一状态是包含dataProvider的状态
  • 它只包含一种方法

将它放在一个物体中没有任何好处。

语句只有行为但没有数据的类通常不是一个类。 是完全错的。

将行为提取到单独的类中是重构中的一个好常见的事情。 它可以有状态,但也不需要有状态。 您需要拥有干净的界面,并在您认为必要时实施它们。

此外,无状态类非常适合您在短时间内进行的计算。 您实例化它们(或者,请求某种类型的工厂来获取它们),进行必要的计算,然后将它们扔到垃圾箱中。 您可以随时随地获得适当的行为“版本”。

通常我发现接口的不同实现具有某种状态(例如,在构造函数中设置),但有时类的类型可以完全确定它的行为。

例如:

public interface IExporter
{
    /// <summary>
    /// Transforms the specified export data into a text stream.
    /// </summary>
    /// <param name="exportData">The export data.</param>
    /// <param name="outputFile">The output file.</param>
    void Transform(IExportData exportData, string outputFile);
}

可以实现为

class TabDelimitedExporter : IExporter { ... }
class CsvExporter : IExporter { ... }
class ExcelExporter : IExporter { ... }

要实现从IExportData (无论可能是什么)导出到CSV文件,您可能根本不需要任何状态。 另一方面, ExcelExporter可以具有导出选项的各种属性,但也可以是无状态的。

[编辑]

GetInvoicesGetArticles移动到WebService类意味着您将其实现与WebService类型联系起来。 将它们放在单独的类中将允许您对发票和文章进行不同的实现。 总的来说,将它们分开似乎更好。

更少关注名称。 关于名称的规则只是一个不良做法的经验法则指标。 重点是:

只有行为但没有数据的类通常不是一个类

在您的情况下,看起来您的类同时具有数据和行为,并且它们也可以称为“发票”和“文章”。

这取决于。 许多类以Read和Write动词命名,因为这些类还创建,维护和表示与它们正在读取或写入的数据源的连接。 如果您的课程正在这样做,最好将它们分开。

如果Reader对象只包含解析逻辑,那么将类转换为实用方法是可行的方法。 不过,我会使用比Webservice更具描述性的名称。

我认为这本书提出了如下设计:

class Article : IIArticleReader
{
    // Article data...

    public IList<Article> GetArticles(); 
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM