手把手教你学设计模式-策略模式(轻松学设计模式)

设计模式-策略模式

前言

大家好,今天带大家学习设计模式中使用最频繁也是最简单的一种模式 “策略模式”,那先说一下为什么要用设计模式,或者换句话什么场景要用策略模式。当然,大家要注意,这只是一种编程规范,并不是一种规定,一定要分清规范和规定的区别,这样说就是要告诉大家不要因为用设计模式而用设计模式,给大家举个简单的例子。

比如你拿100元去超市买了一瓶哇哈哈(5元),这时候收银员需要找给你95元,他可以选择找给你一张50元,两张20元,一张5元,很显然这是非常好的一种策略,节省双方时间。但是收银员却偏偏找给你 95张1元的,恐怕这时候你心里会问候他全家了。但就事实来说,这两种找钱策略都没有问题,因为你收到了95元,这种行为虽然不违法,但是你心里肯定觉得这家伙有毛病。
在这里插入图片描述

通过这个例子,大家可以看出,一般正常人都会选择第一种找钱的策略,这可没有人去规定必须这样做,大家默认的一种共识,就可以理解为某个层面的“规范”。
带入到我们程序中也是这样的,一个功能 有N中实现方式,但如果你偏要选择一种繁琐、难以维护的方式去做,那么后期难受的不止你一个人,还有后期替你维护的人,这就是大部分公司都会有一堆“屎山”的原因。

正文

假设现在有这样一个需求要求你去做,公司内用户都有一个等级分别是LEVEL_0 ~ LEVEL_3,每月月初(1号)用户在登陆后会根据等级不同而赠送不同的积分和金币
规则如下:
LEVEL_0 ⇒ 什么也不赠送
LEVEL_1 ⇒ 20积分
LEVEL_2 ⇒ 30积分+10金币
LEVEL_3 ⇒ 50积分+30金币

当你接收到这样一个需求时,你会怎样去做?
在这里插入图片描述

先给大家写个伪代码

	  //存储用户信息
      Map<String,String> userInfo = new HashMap<String, String>(){
          {
               put("id","u001");
               put("name","张三");
               put("level","LEVEL_3");
           }
       };
	   //获取用户等级
       String level = userInfo.get("level");
	   //判断用户属于哪个等级,从而做出不同的操作
       if("LEVEL_0".equals(level)){
           System.out.println("什么也不赠送");
       }else if("LEVEL_1".equals(level)){
           System.out.println("赠送20积分");
       }else if("LEVEL_2".equals(level)){
           System.out.println("赠送30积分");
           System.out.println("赠送10金币");
       }else if("LEVEL_3".equals(level)){
           System.out.println("赠送50积分");
           System.out.println("赠送30金币");
       }

这种是不是很多的写法,当然不可否认,这样写也可以完美解决这个需求,并且就从目前来看,代码量也很少,一眼就能看清楚。

但请大家思考一下,一般一个大型的项目是由多人协同开发的,可能这个迭代周期你负责维护,下一个周期是别人在维护,当需求变更时,你就会在这对if else中加入更多的代码以及更多的if else if else,或者switch。

在迭代的过程中,慢慢的没人愿意去动这块的代码,因为风险很大,每次变更需求后需要进行一次全量测试。因为你改变了这块代码,但这块代码中有N中可能,你必须保证每种可能执行的结果都能满足预期。
经过n次迭代后,你的代码结构能如下:

	  //存储用户信息
      Map<String,String> userInfo = new HashMap<String, String>(){
          {
               put("id","u001");
               put("name","张三");
               put("level","LEVEL_3");
           }
       };
	   //获取用户等级
       String level = userInfo.get("level");
       if(xx){
       //dosometing
	       if(xx){
	       //dosomething
	       }
       }else{
       	//dosomething
       }
	   //判断用户属于哪个等级,从而做出不同的操作
       if("LEVEL_0".equals(level)){
	       	if(xx){
	       		//dosomething
	       	}
           System.out.println("什么也不赠送");
       }else if("LEVEL_1".equals(level)){
       		if(xx){
	       		//dosomething
	       	}else{
	       		System.out.println("赠送20积分");
	       	}
       }else if("LEVEL_2".equals(level)){
       		if(xx){
	       		//dosometing
	       		System.out.println("赠送30积分");
	       	}else{
	       		System.out.println("赠送20积分");
	       	}
           //dosomething
           
       }else if("LEVEL_3".equals(level)){
           System.out.println("赠送50积分");
           System.out.println("赠送30金币");
       }

你会发现,你的代码逐渐往失控的方向走去,没人愿意看到这样一坨一坨的代码,因为他的结构并不清晰,单元测试量也会提升,导致每次修改一点代码,都需要对所有可能进行回归验证。

如果你不想看到这样的事情发生,那就接着往下看吧。

为了避免一些字面量的if else,我们首先将他们定义到枚举中,因为枚举类会让你的代码变的没那么容易出现简单的错误,比如 你将

if(“LEVEL_3”.equals(level))

错误的拼写为

if("LEEVL_3".equals(level))

可以很大程度避免这些低级错误,当然他的好处不止这些,能让后期维护你代码的人一眼能看出允许哪些值的出现。

首先,我们先定义原有两个业务接口,用户积分service和用户金币service,其中各自有一个方法,分别为用户增加积分和金币。

//用户积分service
public class UserScoreService {

    /**
     * 向用户增加N个积分
     * @param userName
     * @param score
     * @return
     */
    public boolean addUserScore(String userName,long score){
        System.out.println(String.format("为用户%s增加%s个积分",userName,score));
        return true;
    }
}

//用户金币service
public class UserGoldService {

    /**
     * 向用户增加N个金币
     * @param userName
     * @param goldNumber
     * @return
     */
    public boolean addUserGold(String userName,long goldNumber){
        System.out.println(String.format("为用户%s增加%s个金币",userName,goldNumber));
        return true;
    }
}

接下我,我们先定义一个枚举类,如下:

public enum UserLevelEnum {

    LEVEL_0,
    LEVEL_1,
    LEVEL_2,
    LEVEL_3;

    private static Map<String,UserLevelEnum> KV_CACHE = new HashMap<String, UserLevelEnum>(){
        {
            for (UserLevelEnum level : UserLevelEnum.values()) {
                put(level.name(),level);
            }
        }
    };


    public static UserLevelEnum getByName(String name){
        return KV_CACHE.get(name);
    }
}

该枚举类中包含了LEVEL_0~LEVEL_1的枚举项。

有了枚举类之后,我们该定义一个接口

public interface UserScoreProcessor {

    UserLevelEnum support() throws Exception;

    void process(Map<String,String> userInfo) throws Exception;

    Integer order();

}
  • UserLevelEnum support() 用来返回一个当前处理器可以处理的类型
  • void process(Map<String,String> userInfo) 入参为用户信息实体,用于处理具体业务
  • Integer order() 处理器的排序,按照升序去执行

我们将接口定义好之后,按照道理应该去实现对吧?但是为了以后容易扩展,建议大家做一个适配层,也就是 适配器模式的一种实现,具体如下。

public abstract class UserScoreProcessorAdapter implements UserScoreProcessor {

    private static Integer DEFAULT_ORDER = 0;

    private UserScoreService userScoreService;

    private UserGoldService userGoldService;

    public UserScoreProcessorAdapter(){
        this.userGoldService = new UserGoldService();
        this.userScoreService = new UserScoreService();
    }

    @Override
    public UserLevelEnum support() throws Exception {
        throw new Exception("此处理器暂不支持");
    }

    @Override
    public void process(Map<String, String> userInfo) throws Exception {

    }

    @Override
    public Integer order() {
        return DEFAULT_ORDER;
    }

    protected boolean addUserScore(String userName,long score){
        return userScoreService.addUserScore(userName,score);
    }

    protected boolean addUserGold(String userName,long goldNumber){
        return userGoldService.addUserGold(userName,goldNumber);
    }
}

可以看到,我们定义了一个抽象类,去实现了刚定义的接口,并将方法进行了默认实现,将 ScoreService和 GoldService聚合到了抽象类中。

接下来,我们该创建具体的实现类,来实现功能业务了。
在这里插入图片描述可以看到,这里创建了4个实现类,分别去处理LEVEL_0~LEVEL_1的业务。

public class Level0Processor extends UserScoreProcessorAdapter {

    @Override
    public UserLevelEnum support() throws Exception {
        return UserLevelEnum.LEVEL_0;
    }

    @Override
    public void process(Map<String, String> userInfo) throws Exception {
        String name = userInfo.get("name");
        System.out.println("当前用户为LEVEL_0 ,不做任何操作");
    }
}

public class Level1Processor extends UserScoreProcessorAdapter {

    @Override
    public UserLevelEnum support() throws Exception {
        return UserLevelEnum.LEVEL_1;
    }

    @Override
    public void process(Map<String, String> userInfo) throws Exception {
        String name = userInfo.get("name");
        addUserScore(name,20);
    }
}

public class Level2Processor extends UserScoreProcessorAdapter {

    @Override
    public UserLevelEnum support() throws Exception {
        return UserLevelEnum.LEVEL_2;
    }

    @Override
    public void process(Map<String, String> userInfo) throws Exception {
        String name = userInfo.get("name");
        addUserScore(name,30);
        addUserGold(name,10);
    }
}

public class Level3Processor extends UserScoreProcessorAdapter {

    @Override
    public UserLevelEnum support() throws Exception {
        return UserLevelEnum.LEVEL_3;
    }

    @Override
    public void process(Map<String, String> userInfo) throws Exception {
        String name = userInfo.get("name");
        addUserScore(name,50);
        addUserGold(name,30);
    }
}

可以看到,我们在每个实现类中都去重写了 process() 方法,针对每个等级,做了不同的业务处理。
到现在为止,我们的工作已经完成大部分了,接下来我们该去思考,如何让他们按照用户等级不同去调用对应的实现类。

首先,我们应该有一个类,这个类中应该包含等级和具体实现的映射,能够根据不同的用户等级去调用我们事先定义好的处理类,有了思路,就开始实现吧。

public class UserScoreProcessorFactory extends UserScoreProcessorAdapter{

    /**
     * 简易单例
     */
    public static UserScoreProcessorFactory INSTANCE = new UserScoreProcessorFactory();

    /**
     * 保存 用户等级 与 具体处理类的实现
     */
    private static Map<UserLevelEnum, List<UserScoreProcessor>> processors = new HashMap<>(16);


    static {
        try {
            /**
             * 将我们实现的处理类注入到 工厂类中
             */
            register(new Level0Processor());
            register(new Level1Processor());
            register(new Level2Processor());
            register(new Level3Processor());

            /**
             * 对处理类进行排序
             */
            processors.values().forEach(e -> {
                e.sort((o1,o2) -> o1.order()-o2.order());
            });
        }catch (Throwable t){
            t.printStackTrace();
        }
    }

    private UserScoreProcessorFactory(){

    }


    /**
     * 将处理类注册到工厂类中(也可以说是策略上下文)
     * @param processor
     * @return
     * @throws Exception
     */
    public static UserScoreProcessor register(UserScoreProcessor processor) throws Exception {
        UserLevelEnum support = processor.support();
        List<UserScoreProcessor> userScoreProcessors = processors.get(support);
        if(userScoreProcessors==null){
            List<UserScoreProcessor> list = new ArrayList<UserScoreProcessor>();
            processors.put(support,list);
            userScoreProcessors = list;
        }
        if(!userScoreProcessors.contains(processor)){
            userScoreProcessors.add(processor);
        }
        return processor;
    }


    @Override
    public void process(Map<String, String> userInfo) throws Exception {
        UserLevelEnum level = UserLevelEnum.getByName(userInfo.get("level"));
        List<UserScoreProcessor> userScoreProcessors = processors.get(level);
        if(userScoreProcessors == null || userScoreProcessors.isEmpty()){
            throw new Exception("没有支持此类型的处理器");
        }
        executeProcessor(level,userInfo);
    }

    private void executeProcessor(UserLevelEnum level,Map<String, String> userInfo) throws Exception {
        List<UserScoreProcessor> userScoreProcessors = processors.get(level);

        for (UserScoreProcessor userScoreProcessor : userScoreProcessors) {
            userScoreProcessor.process(userInfo);
        }
    }
}

可以看到,在我们工厂类中,我们定义一个Map去存储用户等级与具体处理器的映射,在静态代码块中,我们将所有处理器注册进了map中。
process()方法中,我们先获取用户的等级,然后获取其对应的枚举值,通过map获取到对应的处理器集合,最终循环去调用对应的处理器。
到这里我们就完成了整个业务功能的开发。

运行一下试试吧。

public static void main(String[] args) throws Exception {

        UserScoreProcessorFactory factory = UserScoreProcessorFactory.INSTANCE;

        Map<String,String> userInfo = new HashMap<String, String>(){
            {
                put("id","u001");
            }
        };


        userInfo.put("name","李四");
        userInfo.put("level","LEVEL_0");
        //LEVEL_0
        factory.process(userInfo);

        userInfo.put("name","王五");
        userInfo.put("level","LEVEL_1");
        //LEVEL_1
        factory.process(userInfo);

        userInfo.put("name","赵六");
        userInfo.put("level","LEVEL_2");
        //LEVEL_2
        factory.process(userInfo);

        userInfo.put("name","钱七");
        userInfo.put("level","LEVEL_3");
        //LEVEL_3
        factory.process(userInfo);
    }

执行结果:

当前用户为LEVEL_0 ,不做任何操作

为用户王五增加20个积分

为用户赵六增加30个积分
为用户赵六增加10个金币

为用户钱七增加50个积分
为用户钱七增加30个金币

后言:

大家在学习设计模式时,一定要注意,他只是一种规范,并没有规定一定要按照哪种结构去写。同时也不要为了用设计模式而去学设计模式,不要硬套设计模式,那么会产生反向的效果。

如果喜欢的话点个关注吧,有任何问题可以私信我,有问必答。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

顿悟的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值