【问题标题】:Single function classes (named as verbs)单个函数类(命名为动词)
【发布时间】:2017-03-04 15:23:40
【问题描述】:

我正在尝试一种新的代码结构,我将我所有的大型存储库和工厂拆分为大量较小的类,每个类都有一个单一的职责。除此之外,我使用动词作为类名,因为我认为这是准确描述每个类的含义的最佳方式。

每个类只有一个公共方法(称为“Execute”),但通常有私有方法,有时还有一个带参数的构造函数。

示例

之前:

class DocumentRepository {
  public List<Document> GetDocuments()
  public void SaveDocument(Document document)
  public Document CopyDocument(int id)
}

之后:

class GetDocuments {
  public List<Document> Execute()
}
class SaveDocument {
  public void Execute(Document document)
}
class CopyDocument {
  public Document Execute(int id)
}

这种结构的一个好处是我更愿意将公共方法的功能拆分为多个私有方法(更易于阅读和管理)。在此之前会增加存储库中的混乱,但现在私有方法包含在它们单独的类中。

我一直读到,类应该有名词作为名称,但是当一个类只有一种用途时,似乎最好用它的作用来命名它。

问题

这是一个坏主意(分离和命名)?如果是这样,创建这种分离并避免大类的更好方法是什么?

编辑: 应该注意的是,我是从命令查询的角度得出这些(奇怪的)想法的。所以也许将它与那个而不是存储库进行比较是最有意义的。

【问题讨论】:

  • 这可能最好放在codereview.stackexchange.com。也就是说,我会说类是名词,方法是动词,但都是意见。
  • 我实际上是在一个个人项目中玩弄相同的命名想法,并用谷歌搜索看看其他人是怎么想的。在我的例子中,它不是一个存储库,而是类似的——一个具有单个公共方法Invoke、许多私有方法和一些依赖项的类。答案说这个命名方案很奇怪。但如果它有助于最好地表达一个班级的目的,也许不要被教条所困并无论如何都要使用它?

标签: c# .net naming-conventions command-query-separation


【解决方案1】:

虽然将每个方法都提取到单独的类中似乎过于激进,但其中还是有一定意义的。对于高负载系统,正在应用命令-查询职责分离 (CQRS) 原则。它的主要思想是将查询(幂等方法,不修改系统状态,而只返回数据)与命令(修改系统状态的方法;在“纯”CQRS 中它们甚至不返回数据 - 尽管它仍然是我很难接受这一点)。

这样做会使您的系统更加稳定且易于扩展,因为您可以拥有许多除了读取之外什么都不做的分布式节点,专注于修改数据的单个节点的性能。然而,并非所有系统都真正需要这个。

即使您确实需要为单个方法创建一个单独的类,将其命名为动词也不是一个好主意,因为它会使其他开发人员感到困惑(留在一个类中很难确定您在代码中看到的内容 - 另一个类名或此类的内部方法)。您不需要在这里发明自己的符号,只需使用以下(相对标准):

class GetDocumentsQuery { }

class SaveDocumentCommand { }

class CopyDocumentCommand { }

更多关于 CQRS 的信息:

  1. https://en.wikipedia.org/wiki/Command%E2%80%93query_separation

  2. http://rob.conery.io/2014/03/04/repositories-and-unitofwork-are-not-a-good-idea/

【讨论】:

  • 我实际上是通过阅读和探索 Command-Query 的想法得出这些想法的。而且我想我正试图在这些原则附近的某个地方找到我的舒适之处。但是我看到你的观点,即其他开发人员对动词名称感到困惑,即使唯一的区别是在类名之后添加“查询”或“命令”。
  • 当您说“纯”CQRS 命令不返回数据时,您的意思可能非常不同。但请注意,在 CQRS 中,命令不必是单向的。 Here is Greg Young syaing it
【解决方案2】:

IMO,你的设计很奇怪。

当然,我理解您的目标 - 通过将私有方法移到其他地方来减少类中的噪音,但是将每个方法移到单独的类中似乎太过分了。

以前,您只需要一个对象即可获取、保存和复制文档。现在你需要3个并行!我猜你可以创建一个包装这三个类的包装器,

public class DocumentRepository {
    public readonly GetDocument Get = new GetDocument();
    public readonly SaveDocument Save = new SaveDocument();
    public readonly CopyDocument Copy = new CopyDocument();
}

但还是太复杂了。

如果您想要减少噪音,请尝试以更好的方式组织您的课程,并在您的 IDE 支持的情况下使用 #region#endregion。如果我是你,我会这样做:

public SomeClass {
    // private fields
    ...
    // properties
    ...
    // public methods
    ...
    // private methods
    ...
}

另一种方法是:

public SomeClass {
    // private fields
    ...
    // properties
    ...
    // public method 1
    ...
    #region
    // private methods related/called by public method 1
    ...
    #endregion
    // public method 2
    ...
    #region
    // private methods related/called by public method 2
    ...
    #endregion
    // public method 3
    ...
    #region
    // private methods related/called by public method 3
    ...
    #endregion

    #region
    // other private methods that are not related to a particular public method.
    #endregion
}

【讨论】:

  • 问题是,这些方法(或者在我的例子中是类),除了处理相同类型的对象之外,真的没有任何关系。我不需要将它们包装在一起,除了将它们放在同一个文件夹中,因为它们在域的同一部分中运行。它们中的每一个都是从一个单独的控制器动作中调用的。
  • 这听起来像你应该创建一个实用程序类。如果DocumentRepository 没有状态(没有属性或字段),您应该只创建一个包含这些方法的静态实用程序类,按照我在答案中描述的方式组织。 @svejk
  • Static 也不适合,因为它们确实利用了继承(SaveDocument-class 是 SaveEntity-class 的子类)。而且我发现使用可实例化的类管理单元测试要容易得多(在必要时创建这些类的模拟对象)。但你说得对,它们根本不持有任何状态,它们只是应用程序中特定任务的执行者。
【解决方案3】:

这是一个坏主意(分离和命名)?

我会说命名主要是一个品味问题。然而,你的新设计简直太糟糕了。

您应该以逻辑方式将功能相关的方法组合在一起,通常基于对其执行操作的实体。我会说您的GetSave 等方法属于存储库模式类或实体类本身。 从不我会把方法变成类,这根本没有意义。

我可以理解您希望减少每个班级的行数。也许最好使用基类来放入通用代码,或者将一些重复的动作拆分到一个单独的类中。

【讨论】:

  • 感谢您的意见,如果您从 Command-Query 的角度来看,作为 Aleksei 的引用,您认为我所做的更有意义吗?
  • 对我来说似乎太激进了,在大多数情况下都没有用。
猜你喜欢
  • 1970-01-01
  • 2018-01-31
  • 2022-08-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-04
相关资源
最近更新 更多