仅仅因为一个系统是复杂的并不意味着你必须使它变得复杂。如果您的类具有过多的依赖性(或协作者),例如:
public class MyAwesomeClass {
public class MyAwesomeClass(IDependency1 _d1, IDependency2 _d2, ... , IDependency20 _d20) {
// Assign it all
}
}
...然后变得太复杂了,您并没有真正遵循SRP,是吗?如果你写下什么我敢打赌MyAwesomeClass
一上做CRC卡它不适合索引卡上,或者你必须写在真的很小潦草的信件。
您在这里所拥有的是,您的家伙们只遵循了接口隔离原则,并且可能将其推向了极致,但这完全是另外一回事了。您可能会争辩说,依赖项是域对象(会发生这种情况),但是拥有一个同时处理20个域对象的类会使它有点过分。
TDD将为您很好地指示一堂课的成绩。直言不讳 如果测试方法的安装代码需要花费大量时间编写(即使您重构测试),那么您MyAwesomeClass
可能还有太多事情要做。
那么如何解决这个难题呢?您将职责移至其他班级。您可以对有此问题的类采取一些步骤:
- 确定类对其依赖项执行的所有操作(或职责)。
- 根据密切相关的依赖项将操作分组。
- 重新委托!即将每个已识别的动作重构为新的类别(或更重要的是其他类别)。
重构责任的抽象示例
我们C
是有一些依赖性的一类D1
,D2
,D3
,D4
,你需要重构少用。当我们确定C
调用依赖项的方法时,我们可以列出它的简单列表:
D1
- performA(D2)
,performB()
D2
-- performD(D1)
D3
-- performE()
D4
-- performF(D3)
查看列表,我们可以看到,D1
并且D2
彼此相关,因为班级以某种方式需要它们。我们也可以看到D4
需求D3
。因此,我们有两个分组:
Group 1
- D1
<->D2
Group 2
- D4
- >D3
分组表明班级现在有两个职责。
Group 1
-一个用于处理彼此需要的两个调用对象。也许您可以让您的类C
消除处理两个依赖关系的需要,而让其中一个处理这些调用。在此分组中,显然D1
可以参考D2
。
Group 2
-其他责任需要一个对象来调用另一个。无法D4
处理D3
,而不是你的班?然后,我们可以改为通过调用来D3
从类中消除。C
D4
不要把我的答案一成不变,因为该示例是非常抽象的,并且有很多假设。我敢肯定,还有更多方法可以重构它,但是至少这些步骤可以帮助您获得某种过程来转移职责,而不是拆分类。
编辑:
在评论中,@ Emmad Karem说:
“如果您的类在构造函数中有20个参数,这听起来并不像您的团队完全知道SRP是什么。如果您的类只做一件事,那么它如何有20个依赖项?”-我认为,如果有一个Customer类,在构造函数中有20个参数并不奇怪。
确实,DAO对象倾向于具有很多参数,您必须在构造函数中设置这些参数,并且这些参数通常是简单的类型,例如字符串。但是,在一个Customer
类的示例中,您仍然可以将其属性分组到其他类中,以简化操作。例如,有一个Address
带有街道的Zipcode
类和一个包含邮政编码的类,该类还将处理诸如数据验证之类的业务逻辑:
public class Address {
private String street1;
//...
private Zipcode zipcode;
// easy to extend
public bool isValid() {
return zipcode.isValid();
}
}
public class Zipcode {
private string zipcode;
public bool isValid() {
// return regex match that zipcode contains numbers
}
}
在博客文章“永不,永不,永远不要在Java中使用String(或至少经常)”中对此问题进行了进一步讨论。作为使用构造函数或静态方法使子对象更易于创建的一种替代方法,可以使用流体生成器模式。