如果您在团队中并且程序员为您提供了创建,读取,更新和删除方法的界面,那么如何避免类型切换?
引用清洁代码敏捷软件工艺手册:
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语句埋在 一个抽象工厂的地下室,9,永远不要让任何人看到它。
switch语句难看吗?
答案 0 :(得分:7)
实际上,员工对象应该有自己的计算工资函数,可以为您提供工资。这个计算工资函数会根据员工的类型而改变。
这样就由对象来定义实现,而不是对象的用户。
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代码。但是再一次,论证应该适用于任何语言。
答案 1 :(得分: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>
答案 2 :(得分:1)
我在某个地方读到它,如果你使用的是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并不是真正的多态,因此在典型的类型安全语言中发现的某些多态行为可能无法按预期工作。