如何重构这个大方法?

时间:2014-06-03 07:25:23

标签: php refactoring

我为徽章激活写了一个类,现在应该重构了。 有什么好的建议,我应该如何重构它的trigger()方法?

该类的源代码在github中: https://github.com/heal25/ced/blob/master/models/BadgeActivator.php

有问题的方法:

public function trigger($id, $data = []) {
    if (!$this->_uid) $this->setUid(Yii::app()->player->model->uid); //set default uid

    $activate = false;

    switch ($id) {
        //case 'login_1': $activate = true; break;
        case 'max_nrg_35': if ($data['energy_max'] >= 35) $activate = true; break;
        case 'max_nrg_100': if ($data['energy_max'] >= 100) $activate = true; break;
        case 'skill_35': if ($data['skill'] >= 35) $activate = true; break;
        case 'skill_100': if ($data['skill'] >= 100) $activate = true; break;
        case 'strength_35': if ($data['strength'] >= 35) $activate = true; break;
        case 'strength_100': if ($data['strength'] >= 100) $activate = true; break;
        case 'energy_drink': $activate = true; break;
        case 'level_10': if ($data['level'] >= 10) $activate = true; break;
        case 'level_100': if ($data['level'] >= 100) $activate = true; break;
        case 'dollar_50': if ($data['dollar'] >= 50) $activate = true; break;
        case 'dollar_5000': if ($data['dollar'] >= 5000) $activate = true; break;

        case 'travel_loc3': if ($data['water_id'] == 3) $activate = true; break;
        case 'travel_county2': if ($data['county_id'] == 2) $activate = true; break;
        case 'travel_county9': if ($data['county_id'] == 9) $activate = true; break;
        case 'routine_100': if ($data['routine'] >= 100) $activate = true; break;
        case 'loc_routine_4b': if ($data['water_id']==4 and $data['routine'] > 0) $activate = true; break;
        case 'loc_routine_13s': if ($data['water_id']==13 and $data['routine'] >= 3) $activate = true; break;
        case 'loc_routine_28s': if ($data['water_id']==28 and $data['routine'] >= 3) $activate = true; break;
        case 'loc_routine_37g': if ($data['water_id']==37 and $data['routine'] >= 9) $activate = true; break;
        case 'loc_routine_52b': if ($data['water_id']==52 and $data['routine'] > 0) $activate = true; break;
        case 'loc_routine_61s': if ($data['water_id']==61 and $data['routine'] >= 3) $activate = true; break;
        case 'loc_routine_71g': if ($data['water_id']==71 and $data['routine'] >= 9) $activate = true; break;
        case 'loc_routine_72e': if ($data['water_id']==72 and $data['routine'] >= 27) $activate = true; break;
        case 'loc_routine_46d': if ($data['water_id']==46 and $data['routine'] >= 81) $activate = true; break;
        case 'setpart_3': if ($data['cnt'] >= 3) $activate = true; break;
        case 'setpart_10': if ($data['cnt'] >= 10) $activate = true; break;
        case 'setpart_30': if ($data['cnt'] >= 30) $activate = true; break;
        case 'first_duel_win': if ($data['role'] == 'caller' and $data['winner'] == 'caller') $activate = true; break;
        case 'duel_success_100': if ($data['cnt'] >= 100) $activate = true; break;
        case 'duel_fail_100': if ($data['cnt'] >= 100) $activate = true; break;
        case 'duel_rate_40': if ($this->getSuccessRate(100, $data) <= 40) $activate = true; break;
        case 'duel_rate_25': if ($this->getSuccessRate(300, $data) <= 25) $activate = true; break;
        case 'duel_rate_10': if ($this->getSuccessRate(600, $data) <= 10) $activate = true; break;
        case 'duel_rate_60': if ($this->getSuccessRate(100, $data) >= 60) $activate = true; break;
        case 'duel_rate_75': if ($this->getSuccessRate(300, $data) >= 75) $activate = true; break;
        case 'duel_rate_90': if ($this->getSuccessRate(900, $data) >= 90) $activate = true; break;
        case 'duel_money_100': if ($data['dollar'] >= 100) $activate = true; break;
        case 'duel_money_1000': if ($data['dollar'] >= 1000) $activate = true; break;
        case 'duel_win_chance35': if ($data['winner'] and $data['chance'] <= 35) $activate = true; break;
        case 'duel_win_chance20': if ($data['winner'] and $data['chance'] <= 20) $activate = true; break;
        case 'duel_win_chance5': if ($data['winner'] and $data['chance'] <= 5) $activate = true; break;
        case 'duel_lose_chance65': if (!$data['winner'] and $data['chance'] >= 65) $activate = true; break;
        case 'duel_lose_chance80': if (!$data['winner'] and $data['chance'] >= 80) $activate = true; break;
        case 'duel_lose_chance95': if (!$data['winner'] and $data['chance'] >= 95) $activate = true; break;
        case 'duel_2h': if ($data['role'] == 'caller' and date('G')==2) $activate = true; break;
        case 'shop_item10': if (Yii::app()->player->model->owned_items >= 10) $activate = true; break;
        case 'shop_bait20': if (Yii::app()->player->model->owned_baits >= 20) $activate = true; break;
        case 'set_b': if ($data['id']==1) $activate = true; break;
        case 'set_s': if ($data['id']==2) $activate = true; break;
        case 'set_g': if ($data['id']==3) $activate = true; break;
        case 'set_sell_b': if ($data['id']==1) $activate = true; break;
        case 'set_sell_s': if ($data['id']==2) $activate = true; break;
        case 'set_sell_g': if ($data['id']==3) $activate = true; break;
        case 'club_join': $activate = true; break;
        case 'club_create': $activate = true; break;
        case 'club_members_8': if ($data['cnt'] >= 8) $activate = true; break;
        case 'login_days_7': if ($this->getLoginDays() >= 7) $activate = true; break;
        case 'login_days_30': if ($this->getLoginDays() >= 30) $activate = true; break;
        case 'login_days_60': if ($this->getLoginDays() >= 60) $activate = true; break;
        case 'win_contest': $activate = true; break;
    }

    if ($activate) {
       return $this->activate($id);
    }
    return false;
}

我可以提取'loc_routine_ *'案例的激活,但并不多。

1 个答案:

答案 0 :(得分:2)

我不熟悉Yii,但是通过浏览他们的文档,你可能会重构trigger方法来使用Yii Validators和DI容器。这些方面的东西:

class BadgeActivator
…
    public function trigger($id, array $data)
    {
        $badgeValidator = $this->badgeValidators->findById($id);

        if ($badgeValidator->validate($data)) {
            $this->activate($id);
        }
    }
}

BadgeValidators对象是实现BadgeValidator接口的简单对象集合。您需要将此注入BadgeActivator

class BadgeValidators
{
    private $validators = [];

    public function __construct(array $badgeValidators)
    {
        foreach ($badgeValidators as $badgeValidator) {
            $this->addBadgeValidator($badgeValidator);
        }
    }

    public function addBadgeValidator(BadgeValidator $badgeValidator)
    {
        $this->validators[$badgeValidator->getId()] = $badgeValidator;
    }

    public function findById($badgeId)
    {
        if (isset($this->validators[$badgeId])) {
            return $this->validators[$badgeId];
        }

        throw new BadgeValidatorNotFoundException(
            "No BadgeValidator found for badge id [$badgeId]"
        );
    }
}

BadgeValidator接口是所有具体验证者需要遵守的合同。他们的目的是从$data映射到Yii的验证器构建或封装附加/唯一验证逻辑。:

interface BadgeValidator
{
    public function getId();
    public function validate($value = null);
}

具体的Validator将如下所示:

class MaxEnergy35 implements BadgeValidator
{
    public function getId()
    {
        return 'max_nrg_35';
    }

    public function validate($value = null)
    {
        $validator = new yii\validators\IntegerValidator();
        $validator->min = 35;

        return $validator->validate($value['max_energy']);
    }
}

您也可以将此课程的配置移至DI配置,例如你可以注入maxEnergy,validatorId和Yii Validator。如果你这样做,你将只有一个类MaxEnergy而不是MaxEnergy35和MaxEnergy100。为了简单起见,我现在就这样保留它。

以下是自定义逻辑:

class DuelRate40 implements BadgeValidator
{
    public function getId()
    {
        return 'duel_rate_40';
    }

    public function validate()
    {
        return $this->getSuccessRate(100, $data) <= 40;
    }

    private function getSuccessRate($limit, array $data)
    {
        // moved from BadgeActivator
    }    
}

正如您所看到的,使用自定义逻辑并且不使用Yii验证器来制作BadgeValidators是微不足道的。在这种特殊情况下,我只是将getSuccessRate rate方法移到了验证器上。您可以很容易地看到它在BadgeActivator上放错位置,因为它没有任何类属性的内聚力。显然,如果您需要在多个验证中使用此代码,您可以再次对其进行配置,因此您只有一个此类验证器,而不是在每个具体验证器中复制该方法。

现在,对于你的switch / case中的每个case,只需添加另一个具体的BadgeValidator并将其注入DIConfig中的BadgeValidators。

ced/config/main.php

// more config  …
components => [
    'badgeValidator' => [
        'class'=> 'application.components.BadgeValidator',
        'badgeValidators' => [
            ['class'=>'MaxEnergy35'],
            ['class'=>'MaxEnergy100'],
            ['class'=>'DuelRate40'],
            // more validators …
        ]
    ],
    // other components …
]

注意:我不知道这是否是使用Yii的DI配置的正确方法。我只是假设您在GitHub上的文件。但我想,如果有必要,你会知道如何改变它。

无论何时需要添加新徽章,只需编写验证器并在配置中将其连接起来。您永远不需要再次触摸BadgeActivator。这也将显着降低BadgeActivator的Cyclomatic Complexity。为上面的每个组件添加单元测试是微不足道的。