【问题标题】:avoiding type switching避免类型切换
【发布时间】:2010-01-16 21:25:00
【问题描述】:

如果您在一个团队中,并且程序员为您提供了一个包含创建、读取、更新和删除方法的接口,您如何避免类型切换?

引用干净代码敏捷软件工艺手册:

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),因为它必须在添加新类型时发生变化。但这可能是最糟糕的问题 功能是有无限数量的其他功能将具有相同的 结构体。例如我们可以有

isPayday(Employee e, Date date),

deliverPay(Employee e, Money pay),

或其他许多人。所有这些都具有相同的有害结构。

这本书告诉我使用工厂模式,但它让我觉得我不应该真正使用它。

再次引用这本书:

解决这个问题的方法(见清单 3-5)是将 switch 语句埋在 ABSTRACT FACTORY 的地下室,9 永远不要让任何人看到它。

switch语句丑吗?

【问题讨论】:

  • 您可能想接受答案。

标签: switch-statement


【解决方案1】:

实际上,employee 对象应该有自己的计算工资函数,它会为您提供工资。这个计算薪酬函数会根据它是什么类型的员工而改变。

这样由对象来定义实现,而不是对象的用户。

abstract class Employee
{
     public abstract function calculatePay();
}

class HourlyEmployee extends Employee
{
     public function calculatePay()
     {
          return $this->hour * $this->pay_rate;
     }
}

class SalariedEmployee extends Employee
{
     public function calculatePay()
     {
          return $this->monthly_pay_rate;
     }
}

当您构建工厂时,您会在那里执行 switch 语句,并且只执行一次来构建员工。

假设员工在一个数组中,员工的类型保存在$array['Type']

public function buildEmployee($array)
{
    switch($array['Type']){
       case 'Hourly':
            return new HourlyEmployee($array);
            break;
       case 'Salaried':
            return new SalariedEmployee($array);
            break;
}

最后,计算工资

$employee->calculatePay();

现在,无需多个 switch 语句即可根据员工的类型计算员工的工资。它只是员工对象的一部分。

免责声明我是未成年人,所以我对其中一些工资的计算方式并不完全肯定。但论证的基础仍然有效。工资应该在对象中计算。

免责声明 2这是 PHP 代码。但是再一次,这个论点应该对任何语言都有效。

【讨论】:

  • 你可能想从 Employee 扩展这些 :)
  • 是的..我真的希望一个选项卡实际上放置一个选项卡而不是转到页面中的下一个元素
  • 未满 18 岁或毕业于计算机科学专业的未成年人???无论哪种方式都恭喜。
  • @Chacha102 - 这是 ABSTRACT FACTORY 还是原始 GOF 书中介绍的简单工厂成语?
【解决方案2】:

您可以通过使用某种Map 将员工的类型映射到其相应的薪酬计算器来完全移除开关。这取决于反射,并且在我知道的所有语言中都是可能的。

假设工资计算不是员工的责任,我们有一个接口PayCalculation

interface PayCalculation {
    function calculatePay(Employee $employee);
}

每个类别的员工都有一个实现:

class SalariedPayCalculator implements PayCalculation {
    public function calculatePay(SalariedEmployee $employee) {
        return $employee.getSalary();
    }
}

class HourlyPayCalculator implements PayCalculation {
    public function calculatePay(HourlyEmployee $employee) {
        return $employee.getHourlyRate() * e.getHoursWorked();
    }
}

class CommissionedPayCalculator implements PayCalculation {
    public function calculatePay(CommissionedEmployee $employee) {
        return $employee.getCommissionRate() * $employee.getUnits();
    }
}

工资计算将像这样进行。反射对于查看对象并在运行时确定它的类变得很重要。这样就可以消除开关回路了。

public class EmployeePayCalculator implements PayCalculation {

    private $map = array();

    public function __construct() {
        $this->map['SalariedEmployee'] = new SalariedPayCalculator();
        $this->map['HourlyEmployee'] = new HourlyPayCalculator();
        $this->map['CommissionedEmployee'] = new CommissionedPayCalculator();
    }

    public function calculatePay(Employee $employee) {
        $employeeType = get_class($employee);
        $calculator = $this->map[$employeeType];
        return $calculator->calculatePay($employee);
    }
}

这里我们在构造函数中初始化地图,但它可以很容易地移到 XML 配置文件或某个数据库之外:

<payCalculation>
    <category>
        <type>Hourly</type>
        <payCalculator>HourlyPayCalculator</payCalculator>
    </category>
    <category>
        <type>Salaried</type>
        <payCalculator>SalariedPayCalculator</payCalculator>
    </category>
    ...
</payCalculation>

【讨论】:

    【解决方案3】:

    我在某处读到过,如果您使用的是switch,则怀疑存在太多变化。而当我们有太多变体时,我们应该尝试将变体封装在一个接口后面,从而解耦对象之间的依赖关系。话虽如此,我认为您应该尝试创建一个 SalaryType 轻量级基类对象来封装这种类型的逻辑。然后你让它成为class Employee 的成员并摆脱switch 构造。简而言之,这就是我的意思:

    abstract class SalaryType
    {
       function calculatePay() {}
    }
    
    class CommissionedType extends SalaryType
    {
       function calculatePay() {}    
    }
    
    class HourlyType extends SalaryType
    {
       function calculatePay() {}    
    }
    
    class SalaryType extends SalaryType
    {
       function calculatePay() {}    
    }
    
    class Employee
    {
      private $salaryType;
    
      public function setType( SalaryType emp )
      {
         $this->salaryType = emp;
      }
    
      public function calculatePay()
      {
         $this->salaryType->calculatePay();
      }
    }
    

    顺便说一句,你的很多示例代码看起来都不是很“PHP-ish”。 PHP 中没有返回类型,也没有任何类型安全性。另请记住,PHP 并不是真正的多态性,因此在典型类型安全语言中发现的一些多态行为在这里可能无法按预期工作。

    【讨论】:

    • 我认为你打错了一些东西。
    • $私人工资类型;应该仍然是私有的 $salaryType。
    猜你喜欢
    • 2015-08-18
    • 1970-01-01
    • 1970-01-01
    • 2020-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-22
    相关资源
    最近更新 更多