要避免的类(代码完整)
我对代码完整的书中的段落有些困惑。
在“要避免的类”一节中,其内容如下:
“避免以动词命名的类只有行为但没有数据的类通常不是真正的类,考虑将类DatabaseInitialization()或StringBuilder()转换为其他类的例程”
我的代码主要由没有数据的动词类组成。 有invoicereaders,pricecalculators,messagebuilders等我这样做集中每个类的任务。 然后我将其他类的依赖添加到其他function。
如果我正确理解段落,我应该使用类似的代码
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类可能是更清晰和更有意义的OOdevise。
这给你一个简单的,显而易见的名词类名称“WebService”,以及“名词agent-noun”接口名称,清楚地通告WebService类可以为呼叫者做什么。
您也可以通过给另一个名词加前缀来为实际的类赋予更多的含义,例如PaymentWebService。
然而,接口总是比单个类名更好地描述类可以为呼叫者做什么。 随着类越来越复杂,新的接口也可以添加有意义的名称。
我个人无视这个“规则”。 .NET框架本身充满了“动词”类: TextReader
, BinaryWriter
, XmlSerializer
, StringEnumerator
, StringEnumerator
, HttpListener
, TraceListener
, ConfigurationManager
, TypeConverter
, RoleProvider
…如果你认为框架devise不好,不要使用这样的名字。
史蒂夫的意图是可以理解的。 如果你发现自己为了完成一项具体任务而创造了数十个课程,那么这可能就是一个贫血的领域模型的标志,那些能够自己做这些事情的对象不是。 但是在某些时候,你必须在“纯粹的”OOP和SRP之间进行select。
我的build议是这样的:如果你发现自己创build了一个作用于单个“名词”类的“动词”类,诚实地思考“名词”类是否可以自己执行动作。 但是不要开始创造上帝的对象,或者为了避免动词级别而拿出毫无意义/误导性的名字。
不要盲目地遵循任何build议。 这些只是指导方针。
也就是说,只要模仿逻辑对象, 名词就会成为很好的类名 。 由于“Person”类是所有“Person”对象的蓝图,因此将其称为“Person”是非常方便的,因为它允许你这样推理:“我将从用户的input创build一个Person,但首先我需要validation它…“
请注意使用“避免”一词。 如果你曾经使用过它,那么它并不是消除,消除,或是在地狱里燃烧。
作者的意思是,如果你发现自己有一大堆以动词命名的类,而你所碰巧做的就是静态地创build类,调用一个函数而忘记它们,这可能是一个迹象,有点太多的课题。
但是,有些情况下,创build类来执行某个操作是一件好事,例如当您针对同一个操作有不同的策略时。 一个很好的例子是IComparer <>。 它所做的只是比较两件事情,但有几种比较方法。
正如作者所说,在这种情况下,一个好的方法是创build一个接口并实现它。 IComparer <>再次浮现在脑海中。
另一种常见的情况是当动作处于繁重状态时,如加载文件。 将国家封装在课堂上可能是合理的。
本质上,本书所说的OOdevise是关于提取对象(名词)和确定这些对象之间和对象之间发生的操作(动词)。
名词成为对象,动词成为对这些对象进行操作的方法。
这个想法是
程序模拟真实世界的问题越近,程序就越好。
实际上,一个对象的有用之处在于它可以表示一个特定的状态。 那么你可以有几个不同的这个类的实例,每个都持有不同的状态来表示问题的某个方面。
在InvoiceReader类的情况下
- 你只是要创build一个实例
- 它所表示的唯一状态是包含一个dataProvider
- 它只包含一个方法
把它放在一个物体上没有什么好处。
声明一个只有行为但没有数据的类通常不是一个类。 是明显的错误。
将行为提取到一个单独的类中是重构过程中的一个很好和常见的事情。 它可以有状态,但也不需要有状态。 你需要有干净的接口,并且无论如何都要实现它们。
另外,无状态类对于短时间内只需要进行计算非常有用。 你实例化它们(或者请求某种工厂来获取它们),做必要的计算,然后把它们扔到垃圾上。 您可以随时随地获得适当的“版本”行为。
通常我发现一个接口的不同实现有一些状态(例如在构造函数中设置),但有时类的types可以完全确定它的行为。
例如:
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
,另一方面,可以有各种属性的导出选项,但也可能是无状态的。
[编辑]
将GetInvoices
和GetArticles
移入WebService
类意味着您将其实现绑定到WebServicetypes。 让他们在不同的课程中可以让你对发票和文章有不同的实现。 总的来说,让他们分开似乎更好。
关注点名。 关于这个名字的规则只是一个糟糕的做法的经验法则。 重要的一点是:
只有行为但没有数据的类通常不是真正的类
就你而言,看起来你的类同时具有数据和行为,也可以称为“发票”和“文章”。
这取决于。 存在许多以读写动词命名的类,因为这些类还创build,维护并表示与正在读取或写入的数据源的连接。 如果你的课程是这样做的,最好把它们分开。
如果Reader对象只包含parsing逻辑,那么将这些类转换为实用方法是可行的。 虽然我会用比Webservice更具描述性的名称。
我认为这本书提出了如下的devise:
class Article : IIArticleReader { // Article data... public IList<Article> GetArticles(); }
这是OO中“动词与名词”的经典expression:
http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html