策略模式、工厂模式、表驱动实践应用场景–用户任务系统

现在有很多网赚的app或小程序,为了推广和留下用户,都会有一些任务,当用户完成对应的任务,就能够得到相应的奖励。
如果你没有用过这些应用,可以想想王者农药等游戏,它们也有一套任务系统,完成对应的任务,得到相应的奖励。
最近我就接到一个需求,给一个网赚小程序开发一套任务系统。

前置 – 错误示范

其他杂七杂八的需求就不再赘述了,通过上面的介绍大家应该清楚这个任务系统要做成什么样子了。
直奔主题,现在我们遇到一个问题,可能有很多的任务状态都需要去判断是否完成,那么作为一个有经验的工程师,肯定不会这么写…

public boolean getTaskStatus(Long taskId) {
    if (taskId == 1) {
        // task 1
    } else if (taskId == 2) {
        // task 2
    } else if (taskId == 3) {
        // task 3
    }
        ...
        ...
            return false;
}

肯定也不会这么写…

public boolean getTaskStatus(Long taskId) {
    switch (taskId.intValue()) {
        case 1:
            // task 1
            break;
        case 2:
            // task 2
            break;
        ...
        ...
        default:
            break;
    }
    return false;
}

对于这种有大量if esle / swatch case的代码,随着时间的推移会慢慢变成火葬场…
一是因为随着任务的增加要不断的增加esle if / case;
二是做的判断太多,整个类会非常大。
这个时候今天的主角– 策略模式 就上线了。

正确姿势 – 策略模式

在我们当前的需求中,每个任务都需要判断是否完成,但是不同任务的判断条件又不一样。
可以理解为不同的任务,判断是否完成的策略不一样,我们需要根据不同的任务实现不同的策略。
那么我们怎么实现策略模式呢?

  1. 接口

首先,所有的判断都需要做一件事,那么我们将判断这个方法抽取出来,做成一个接口。

public interface ITaskProcess {

    boolean getStatus(Integer id);

}
  1. 策略
    这里就是根据不同任务,实现不同的策略。
    每个任务都有一个策略的实现类,这个类实现了之前抽象的接口。
@Component
public class Task1 implements ITaskProcess {

        @Override
        public boolean getStatus(Integer id) {
            if (id == 1) {
                return true;
            }
            return false;
        }
}
@Component
public class Task2 implements ITaskProcess {

    @Override
    public boolean getStatus(Integer id) {
        if (id == 2) {
            return true;
        }
        return false;
    }
}
  1. 策略上下文
    之后我们需要有个类将策略统一一下,就需要一个上下文来调用策略。
    标准的代码是这样的:
public class TaskContext {

    private ITaskProcess taskProcess;

    public TaskContext(ITaskProcess taskProcess) {
        this.taskProcess = taskProcess;
    }

    public boolean process(Integer id) {
        return taskProcess.getStatus(id);
    }

}

现在我们怎么用呢?

public boolean getStatus() {
    TaskContext taskContext = new TaskContext(new Task1());
    return taskContext.process(1);
}

等、等等,这可不是我们想要的。
第一怎么是自己在new?现在的工程中难道不是应该用Spring来做管理吗?
第二这样不同的任务怎么灵活的调用了?不还是要写if else / swtich case吗?
所以,我们需要改良一下。
首先上文中构造函数改为setter

@Component
public class TaskContext {

    private ITaskProcess taskProcess;

    public void setTaskProcess(ITaskProcess taskProcess) {
        this.taskProcess = taskProcess;
    }

    public boolean process(Integer id) {
        return taskProcess.getStatus(id);
    }

}

为了防止大量的if else / swtich case,我们这里需要用到工厂模式的思想,传入任务id,由工厂创建出对应策略对象。

@Component
public class StrategyFactory implements ApplicationContextAware, ApplicationRunner {
    private ApplicationContext applicationContext;

    private static Map<Integer, ITaskProcess> tableMapping = new HashMap<>();

    @Override
    public void run(ApplicationArguments args) throws Exception {
        tableMapping.put(1, applicationContext.getBean("task1", ITaskProcess.class));
        tableMapping.put(2, applicationContext.getBean("task2", ITaskProcess.class));
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public static ITaskProcess getStrategy(Integer id) {
        return tableMapping.get(id);
    }
}

在策略工厂中,我们还使用到了表驱动。
将策略放到HashMap中,创建策略的时候直接从HashMap中获取策略对象。
当然,因为用到Spring,所以从Spring容器中getBean。
现在使用就很简单了:

@Service
public class TaskService {

    @Autowired
    private TaskContext taskContext;

    public boolean getStatus(Integer taskId) {
        taskContext.setTaskProcess(StrategyFactory.getStrategy(taskId));
        return taskContext.process(taskId);
    }

}

当然,这样其实是有一点点多余,可以再进一步:

@Component
public class TaskContext {

    public boolean process(Integer id) {
        ITaskProcess taskProcess = StrategyFactory.getStrategy(id);
        return taskProcess.getStatus(id);
    }

}

@Service
public class TaskService {

    @Autowired
    private TaskContext taskContext;

    public boolean getStatus(Integer taskId) {
        return taskContext.process(taskId);
    }

}

总结

这样做的好处是什么?
1.避免了大量if else / swtich case,使代码更加优雅,避免了项目成为火葬场。

2.每个类都符合设计模式的单一职责原则,使项目结构、逻辑清晰。

3.符合开闭原则,即以后添加新任务时,不需要改动原来的业务代码,只需要在策略工厂中添加一条任务的配置,把工作注意力放到新任务的策略实现类上就可以了,方便扩展。这也是最大的好处。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据