一种方法应该承担多少责任?

时间:2009-10-19 17:57:06

标签: oop

这肯定是一个与语言无关的问题,而且一直困扰着我很长一段时间。一个例子可能会帮助我解释我面临的两难困境:

让我们说我们有一个方法负责读取文件,用一些对象(存储文件中的信息)填充集合,然后返回集合......如下所示:

public List<SomeObject> loadConfiguration(String filename);

我们还要说,在实现此方法时,如果返回的集合为空(大小为0),则应用程序继续运行似乎是不可行的。现在,问题是,该方法中是否应该进行此验证(检查空集合以及可能后续抛出异常)?或者,这种方法应该唯一的责任是执行文件的加载并忽略验证任务,允许在方法之外的某个后期进行验证吗?

我想一般的问题是:将验证与方法执行的实际任务分离是否更好?通常,这会使事情在稍后阶段变得更容易改变或构建 - 在上面的例子的情况下,在稍后阶段可能会出现这样的情况,其中添加了不同的策略以从空事件中恢复集合是从'loadConfiguration'方法返回的......如果在方法中完成验证(以及产生的异常),这将很难。

也许我在寻求一些教条式的答案时过于迂腐,相反,它只是依赖于使用方法的上下文。无论如何,我会非常有兴趣看到其他人对此有何看法。

全部谢谢!

8 个答案:

答案 0 :(得分:7)

我的建议是坚持单一责任原则,简而言之,每个对象应该有1个目的。在这种情况下,您的方法有3个目的,如果计算验证方面则为4。

以下是关于如何处理此问题以及如何为将来的更新提供大量灵活性的建议。

  1. 保留LoadConfig方法

  2. 让它调用一个新方法来读取文件。

  3. 将上一个方法的返回值传递给另一个方法,以便将文件加载到集合中。

  4. 将对象集合传递给某种验证方法。

  5. 退回收藏。

  6. 最初采用1种方法并将其分为4种,其中一种方法调用其他方法。这应该允许你改变对他人没有任何影响的作品。

    希望这有帮助

答案 1 :(得分:4)

  

我想一般的问题是:是吗?   更好地解决验证问题   由a执行的实际任务   方法

是的。(至少如果你真的坚持回答这样一个普遍的问题 - 找到一个反例也很容易。)如果你把解决方案的两个部分分开,你就是可以交换,删除或重用任何一个。这是一个明显的加分。当然,您必须小心不要通过暴露非验证API来危害对象的不变量,但我认为您已经意识到这一点。你必须做一些额外的打字,但这不会伤害你。

答案 2 :(得分:2)

要将问题转移到更基本的问题,每种方法应尽可能少地 。因此,在您的示例中,应该有一个读取文件的方法,一个从文件中提取必要数据的方法,另一个将该数据写入集合的方法,以及另一个调用这些方法的方法。验证可以采用单独的方法,也可以采用其他方法,具体取决于最有意义的地方。

   private byte[] ReadFile(string fileSpec)
   {  
       // code to read in file, and return contents
   }
   private FileData GetFileData(string fileContents)
   {
       // code to create FileData struct from file contents
   }
   private void FileDataCollection: Collection<FileData> { }

   public void DoItAll (string fileSpec, FileDataCollection filDtaCol)
   {
        filDtaCol.Add(GetFileData(ReadFile(fileSpec)));
   } 

根据需要为每种方法添加验证,验证

答案 3 :(得分:2)

我将通过一个问题回答您的问题:您是否想要针对您的方法产品的各种验证方法?

这与'构造函数'问题相同:在构造期间引发异常或初始化void对象然后调用'init'方法更好吗?你肯定会在这里引起争论! / p>

一般情况下,我建议尽快进行验证:这被称为Fail Fast,它主张尽快发现问题比延迟检测更好,因为诊断是立即的,而后来你会必须恢复整个流程......

如果你不相信,可以这样想:你是否真的想在每次加载文件时写3行? (加载,解析,验证)嗯,这违反了DRY原则。

所以,在那里敏捷:

  • 使用验证编写您的方法:它负责加载有效配置(1)
  • 如果你需要一些参数化,那就添加它(就像'check'参数一样,默认值保留旧的行为当然)

(1)当然,我并不主张一种方法一次完成所有这些......这是一个组织问题:这个方法应该调用专用方法来组织代码:)

答案 4 :(得分:2)

您正在设计API,不应对您的客户做出任何不必要的假设。方法应该只获取它需要的信息,只返回请求的信息,并且只有在无法返回有意义的值时才会失败。

因此,考虑到这一点,如果配置是可加载的但是空的,那么返回一个空列表似乎对我来说是正确的。如果您的客户端在提供空列表时具有特定于应用程序的要求失败,那么它可能会这样做,但未来的客户可能没有该要求。 loadConfiguration方法本身在真正失败时会失败,例如当它无法读取或解析文件时。

但是你可以继续解耦你的界面。例如,为什么必须将配置存储在文件中?为什么我不能提供URL,数据库中的行或包含配置数据的原始字符串?很少有方法应该将文件路径作为参数,因为它将它们紧密地绑定到本地文件系统,并使它们除了核心逻辑之外还负责打开,读取和关闭文件。考虑接受输入流作为替代方案。或者,如果您想允许精心设计的替代方案 - 例如来自数据库的数据 - 请考虑接受ConfigurationReader接口或类似接口。

答案 5 :(得分:0)

方法应该是高度凝聚力的......这是单一的。所以我的意见是按照你的描述分开责任。我有时候很想说...这只是一个简短的方法,所以没关系......然后我会在1.5周后后悔。

答案 6 :(得分:0)

我认为这取决于案例:如果你能想到一个场景,你会使用这个方法并且它返回一个空列表,这没关系,那么我就不会把验证放在方法中。但对于例如一种将数据插入数据库的方法,该数据库必须经过验证(电子邮件地址正确,名称已指定,......)然后将验证代码放入函数并抛出异常应该可以。

答案 7 :(得分:0)

上面没有提到的另一个替代方案是支持依赖注入并让方法客户端注入验证器。这将允许保留“强大的”资源获取是初始化原则,也就是说任何成功加载的对象都是为业务做好准备(Matthieu提到的Fail Fast是相同的概念)。

它还允许资源实现类创建自己的低级验证器,这些验证器依赖于资源的结构,而不会不必要地将客户端暴露给实现细节,这在处理多个不同的资源提供者(如Ryan列出)时非常有用。