要避免的类(代码完成)

时间:2010-01-19 15:33:40

标签: class-design 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,但它也受到“贫血领域模型”的影响。

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

10 个答案:

答案 0 :(得分:20)

类名称如InvoiceReader,PriceCalculator,MessageBuilder,ArticleReader,InvoiceReader实际上不是动词名称。它们实际上是“名词代理 - 名词”类名。请参阅agent nouns

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

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

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

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

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

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

答案 1 :(得分:11)

我个人忽略了这个“规则”。 .NET Framework本身充满了“动词”类:TextReaderBinaryWriterXmlSerializerCodeGeneratorStringEnumeratorHttpListener,{ {1}},TraceListenerConfigurationManagerTypeConverter ...如果您认为框架设计不合理,那么请不要使用这些名称。

史蒂夫的意图是可以理解的。如果您发现自己创建了几十个类来执行特定任务,那么很可能是anemic domain model的标志,应该能够自己做这些事情的对象是不。但在某些时候,你必须在“纯”OOP和SRP之间做出选择。

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

答案 2 :(得分:5)

不要盲目听取任何建议。这些只是指导原则。

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

答案 3 :(得分:3)

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

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

但是,在某种情况下,创建实现操作的类是一件好事,例如当您对同一操作有不同的策略时。一个很好的例子是IComparer&lt;&gt;。它所做的只是比较两件事,但有几种比较方法。

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

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

答案 4 :(得分:2)

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

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

这个想法是

  

更接近程序模型真实   世界问题,方案越好   将是。

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

对于InvoiceReader类

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

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

答案 5 :(得分:1)

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

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

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

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

例如:

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类型联系起来。将它们放在单独的类中将允许您对发票和文章进行不同的实现。总的来说,将它们分开似乎更好。

答案 6 :(得分:0)

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

  

只有行为但没有数据的类通常不是真正的类

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

答案 7 :(得分:0)

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

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

答案 8 :(得分:0)

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

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

    public IList<Article> GetArticles(); 
}

答案 9 :(得分:0)