这是一个关于函数只做一件事的概念的问题。没有一些相关的上下文段落就没有意义,所以我在这里引用它们。它们出现在第37-38页:
然后,他给出了以下不良代码示例:换句话说,我们希望能够像程序一样读取程序 TO段落,每个段落描述当前的抽象级别,并在下一级别引用后续的TO段落。
要包含设置和拆卸,我们包括设置,然后我们包含测试页面内容,然后我们包括拆卸。要包含设置,如果这是套件,我们会包含套件设置,然后我们会包含常规设置。
事实证明,程序员学习遵循这条规则并写作是非常困难的 保持单一抽象级别的函数。但学习这个技巧也非常 重要。保持功能简洁并确保它们做“一件事”是关键。 使代码读取像一个自上而下的TO段落是一种有效的技术 保持抽象级别一致。
public Money calculatePay(Employee e)
throws InvalidEmployeeType {
switch (e.type) {
case COMMISSIONED:
return calculateCommissionedPay(e);
case HOURLY:
return calculateHourlyPay(e);
case SALARIED:
return calculateSalariedPay(e);
default:
throw new InvalidEmployeeType(e.type);
}
}
并解释了它的问题如下:
此功能有几个问题。首先,它很大,而且很新 员工类型被添加,它会增长。其次,它显然不止一件事。 第三,它违反了单一责任原则7 (SRP)因为不止一个 它改变的原因。第四,它违反了开放封闭原则8 (OCP)因为它 每当添加新类型时都必须改变。
现在我的问题。
首先,我很清楚它是如何违反OCP的,而且我很清楚,仅此一点就是设计不佳。但是,我试图理解每个原则,而且我不清楚SRP是如何应用的。具体来说,我可以想象这个方法改变的唯一原因是增加了新的员工类型。只有一个“变革之轴”。如果计算的细节需要改变,这只会影响像“calculateHourlyPay()”这样的子方法
此外,虽然在某种意义上它显然做了3件事,这三件事都处于同一抽象层次,并且都可以被放入一个与段落中没有区别的TO段:来计算薪酬对于员工,如果员工受委托,则计算佣金,如果是每小时,我们计算小时工资等。因此,除了违反OCP之外,这段代码似乎符合Martin对清洁代码的其他要求,即使他认为它没有。
有人可以解释一下我缺少的东西吗?
感谢。
答案 0 :(得分:1)
calculatePay似乎有两个原因需要改变:
两个不同的变化轴。但是,calculatePay方法的责任是工资计算。只有在薪酬计算公式发生变化时才应该更改。我认为这就是作者声明该方法违反SRP的原因。
在本书中,作者提供了一个解决方案,他为每个员工类型定义了从公共Employee抽象基类派生的类。他将calculatePay方法移动到Employee基类,并定义了一个Employee工厂类,该类在给定员工类型的情况下创建适当的员工对象。这样,每个薪资计算都封装在特定的员工类型类中,因此不受员工类型更改的影响。此简单解决方案中的员工工厂类也仅受员工类型更改的影响。因此,新的解决方案让SRP感到高兴。
在新的解决方案中,您要求员工计算他/她不喜欢的工资,因为这并不反映现实。你实际上可以说这也违反了SRP。此计算由薪资部门负责。我喜欢它,软件中的模型尽可能地代表真实世界域,但我们通常必须做出妥协。在这种情况下,我会说员工类型的变化不会定期发生。事实上,他们很可能很少发生。因此,我会将事情保存在他们理智的业务领域,例如要求工资对象计算员工薪酬。与此同时,我会进行并进行广泛的单元测试,因为必须确保当员工类型发生变化时,一切都会按预期继续工作。
答案 1 :(得分:0)
我在这里拍摄远景,因为我没有足够的背景。由于两个原因(通过代码也违反了基本封装原则),此方法可能会发生变化。
在这两种情况下,需要更改的抽象是添加的新Employee类型,而不是Employee类的客户端/用户。我的意思是说(薪水计算应该被封装)calculatePay()方法属于员工抽象的东西石灰下面
interface SalariedEmployee
{
BigDecimal calculatePay();
}
class HourlyEmployee implements SalariedEmployee
{
}
class CommissionedEmployee implements SalariedEmployee
{
}
答案 2 :(得分:0)
我也试图解决这个问题,如果你不是,我想我可能会找到一些东西来说服你。
实际上有两个理由可以改变这种方法: