从ifelse到策略模式,谈谈我对设计模式的理解

前言

一提到设计模式大家都会觉得很厉害,但是要用好设计模式确实不容易。甚至有很多人都不知道该在什么场景下使用设计模式。我之前就是这样,小傅哥的《重学Java设计模式》我也看了,但是看的时候好像看懂了,但是想在自己的项目中运用设计模式时,却不知道如何下手。不过最近在做一个项目时,通过大佬的一番指点,将策略模式运用到了项目之中。后来我仔细思考了一下,好像有点悟了,其实以前做过的很多项目中都可以运用到策略模式,而且使用策略模式后,代码的耦合度会降低扩展性也会增强。

接下来我会结合一个具体的案例,从一开始的不用设计模式,一步步地优化代码,来聊一聊该如何使用策略模式。

从ifelse到策略模式的进化

假如现在有这样一个活动。随机给用户抽取十道题目,如果用户答对其中6道题,就可以获得一份礼品。

这个功能的实现包括抽取题目、判断用户回答正确的题目数、发放礼品等多个环节。现在只针对判断用户回答正确的题目数这一个环节进行讲解。

Step1:一撸到底

现在的需求还是比较简单的,就是循环比对用户的答案与数据库中的答案。直接开撸即可,不需要任何花哨的技巧也可以轻松的完成。

@Service
public class ActivityServiceImpl implements ActivityService {

    @Override
    public Result<Boolean> submitAnswers(AnswersSubmitReq req) {
        if (Objects.isNull(req.getAnswers()) || req.getAnswers().size() < 10) {
            return Result.<Boolean>fail().message("提交的答案数量有误");
        }

        int rightAnswerCount = 0;
        for (AnswerReq answer : req.getAnswers()) {
            // 根据题目id获取从数据库中获取正确答案。此步骤略。假定正确答案是A
            String right = "A";

            rightAnswerCount += StrUtil.equals(right, answer.getUserAnswer()) ? 1 : 0;
        }

        if (rightAnswerCount >= 6) {
            return Result.<Boolean>success().data(true).message("闯关成功");
        }
        return Result.<Boolean>success().data(false).message("闯关失败");
    }

}
复制代码

代码很简单,也很好的实现了功能。如果需求没有进行变更,当然没有问题,但要是需求改变了,代码也要随之更改。

Step2:if...else...

当程序员开发完成后,运维以及产品经理在一起研究讨论发现。现在的活动规则过于简单,少了一些趣味性。为了适当的增加一些趣味性以及挑战性,将整个答题活动分为了三个关卡,关卡由易到难分别为简单、中等、困难,三个关卡都通过才能获得礼品。题目也设为了三个等级:简单、中等、困难

  • 简单模式:10道简单题。答对其中6道即算过关。
  • 中等模式:5道简单题,3道中等题,2道困难题。答对3道简单题,2道中等题,1道困难题即算过关。
  • 困难模式:6道中等题,4道困难题。答对4道中等题,2道困难题即算过关。
@Service
public class ActivityServiceImpl implements ActivityService {

    @Override
    public Result<Boolean> submitAnswers(AnswersSubmitReq req, Integer level) {
        if (Objects.isNull(req.getAnswers()) || req.getAnswers().size() < 10) {
            return Result.<Boolean>fail().message("提交的答案数量有误");
        }
        
        if (level == 1) {
            // 答案的判定。省略…………
        } else if (level == 2) {
            // 答案的判定。省略…………
        } else if (level == 3) {
            // 答案的判定。省略…………
        }
        return Result.<Boolean>success().data(true);
    }

}
复制代码

这个时候,需求的复杂度已经提升了一个等级。虽然从实现上来说也没有什么难度,不过是答案的判断而已。但是当代码写完后会发现,里面有一大坨的if...else...。如果后续需求再次发生变化或者有bug。去定位需要修改的位置也要耗费一定的时间,代码的可维护性就会降低。如果后续再推出第4关,第5关,那么将会有更多的if...else...,所以这种方式也不具备良好的扩展性。最关键的是,这种方式写出来的代码将会很难看,对于一个追求代码整齐、清晰的人来说,简直不能够容忍。

Step3:使用策略模式优化代码

我们先来看一下策略模式的定义:

指定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。

从定义上来看,好像策略模式用起来很不错的样子,那我们就来具体实现一下:

首先在入参的 AnswersSubmitReq 中添加一个字段,用于标识将要采取哪一个策略

@Data
public class AnswersSubmitReq {

	…………

    /**
     * 答题策略
     */
    @NotNull
    private Integer answerMode;

}
复制代码

然后再去新建一个策略接口,所有的策略实现都去实现这个接口👇。

public interface ICommitAnswer {

    Result<Boolean> execute(List<AnswerReq> param);

}
复制代码

👆这个就是策略的接口,策略的实现类都去实现这个接口然后实现其中的execute方法。

public class EasyCommitAnswer implements ICommitAnswer {

    @Override
    public Result<Boolean> execute(List<AnswerReq> param) {
        int rightAnswer = 0;    // 正确回答的数量
        for (AnswerReq answer : param) {
            // 根据题目id获取从数据库中获取正确答案。此步骤略。假定正确答案是A
            String right = "A";

            rightAnswer += StrUtil.equals(answer.getUserAnswer(), right) ? 1 : 0;
        }
        if (rightAnswer >= 6) {
            return Result.<Boolean>success().data(true);
        }
        return Result.<Boolean>fail().data(false);
    }
}
---------------------------------------------------------------------------------------
public class MediumCommitAnswer implements ICommitAnswer {

    @Override
    public Result<Boolean> execute(List<AnswerReq> param) {
        Map<Integer, Integer> rightAnswer = new HashMap<>();
        rightAnswer.put(AnswerStrategyEnum.EASY.getCode(), 0);  // 简单题回答正确的数量
        rightAnswer.put(AnswerStrategyEnum.MEDIUM.getCode(), 0);  // 中等题回答正确的数量
        rightAnswer.put(AnswerStrategyEnum.HARD.getCode(), 0);  // 困难题回答正确的数量
        for (AnswerReq answer : param) {
            // 根据题目id获取从数据库中获取正确答案。此步骤略。假定正确答案是A
            String right = "A";

            int addCount = StrUtil.equals(right, answer.getUserAnswer()) ? 1 : 0;
            rightAnswer.put(answer.getLevel(), rightAnswer.get(answer.getLevel()) + addCount);
        }
        if (rightAnswer.get(AnswerStrategyEnum.EASY.getCode()) >= 3
                && rightAnswer.get(AnswerStrategyEnum.MEDIUM.getCode()) >= 2
                && rightAnswer.get(AnswerStrategyEnum.HARD.getCode()) >= 1) {
            return Result.<Boolean>success().data(true).message("闯关成功");
        }
        return Result.<Boolean>fail().data(false).message("闯关失败");
    }
}
---------------------------------------------------------------------------------------
public class HardCommitAnswer implements ICommitAnswer {

    @Override
    public Result<Boolean> execute(List<AnswerReq> param) {
        Map<Integer, Integer> rightAnswer = new HashMap<>();
        rightAnswer.put(AnswerStrategyEnum.MEDIUM.getCode(), 0);  // 中等题回答正确的数量
        rightAnswer.put(AnswerStrategyEnum.HARD.getCode(), 0);  // 困难题回答正确的数量
        for (AnswerReq answer : param) {
            // 根据题目id获取从数据库中获取正确答案。此步骤略。假定正确答案是A
            String right = "A";

            int addCount = StrUtil.equals(right, answer.getUserAnswer()) ? 1 : 0;
            rightAnswer.put(answer.getLevel(), rightAnswer.get(answer.getLevel()) + addCount);
        }
        if (rightAnswer.get(AnswerStrategyEnum.EASY.getCode()) >= 3
                && rightAnswer.get(AnswerStrategyEnum.MEDIUM.getCode()) >= 2
                && rightAnswer.get(AnswerStrategyEnum.HARD.getCode()) >= 1) {
            return Result.<Boolean>success().data(true).message("闯关成功");
        }
        return Result.<Boolean>fail().data(false).message("闯关失败");
    }

    @Override
    public Result<Boolean> execute(Map<Integer, Integer> rightAnswerCountMap) {
        if (rightAnswerCountMap.get(AnswerStrategyEnum.EASY.getCode()) >= 3
                && rightAnswerCountMap.get(AnswerStrategyEnum.MEDIUM.getCode()) >= 2
                && rightAnswerCountMap.get(AnswerStrategyEnum.HARD.getCode()) >= 1) {
            return Result.<Boolean>success().data(true).message("闯关成功");
        }
        return Result.<Boolean>success().data(false).message("闯关失败");
    }
}
复制代码

现在我们只需要根据不同的场景去调用不同的策略就可以了:

@Service
public class ActivityServiceImpl implements ActivityService {

    @Override
    public Result<Boolean> submitAnswers(AnswersSubmitReq req) {
        if (Objects.isNull(req.getAnswers()) || req.getAnswers().size() < 10) {
            return Result.<Boolean>fail().message("提交的答案数量有误");
        }
        
        List<AnswerReq> answers = req.getAnswers();
        
        if (req.getAnswerMode() == 1) {
            ICommitAnswer answerStrategy = new EasyCommitAnswer();
            return answerStrategy.execute(answers);
        } else if (req.getAnswerMode() == 2) {
            ICommitAnswer answerStrategy = new MediumCommitAnswer();
            return answerStrategy.execute(answers);
        } else if (req.getAnswerMode() == 3) {
            ICommitAnswer answerStrategy = new HardCommitAnswer();
            return answerStrategy.execute(answers);
        }
    }

}
复制代码

策略模式到这里就差不多完成了。具体的策略都由不同的策略实现类决定,与调用方无关,Service层的代码看起来也整齐多了。如果后续某个策略要进行修改,那么去修改对应的策略就好,调用方不需要修改。如果要增加新的策略,那么Service层也只需要进行简单的调整就可以。可维护性与扩展性都大大地得到了提升。

Step4:策略模式再优化

看样子上面的代码好像没有什么问题了,但是Service层在调用策略的时候,不还是要通过if...else...来进行判断吗。只不过是从代码流程的切换变为了对策略调用的判断。

其实这也是可以解决的。首先我们要知道一点,就是外部肯定是知道它自己是要调用哪个策略的,所以我们只需要给每个策略编一个号,外部调用时传个编号过来(上一节的answerMode字段)。我们通过一个Map将所有的策略都装起来,编号就作为Map的key,那通过key不就可以取到对应的策略类了嘛。

public class CommitAnswerFactory {
    private static final Map<Integer, ICommitAnswer> answerStrategies = new HashMap<>();

    static {
        answerStrategies.put(AnswerStrategyEnum.EASY.getCode(), new EasyCommitAnswer());
        answerStrategies.put(AnswerStrategyEnum.MEDIUM.getCode(), new MediumCommitAnswer());
        answerStrategies.put(AnswerStrategyEnum.HARD.getCode(), new HardCommitAnswer());
    }

    public static ICommitAnswer getAnswerStrategy(Integer mode) {
        return answerStrategies.get(mode);
    }

}
复制代码

在策略的工厂类中,通过一个Map将策略的对象放入其中,然后提供一个getAnswerStrategy方法,只要将策略的编号传入,就可以从Map中取出对应的策略实现类了。这样Service层在调用时就不需要使用if...else...进行判断了👇

@Service
public class ActivityServiceImpl implements ActivityService {

    @Override
    public Result<Boolean> submitAnswers(AnswersSubmitReq req) {
        if (Objects.isNull(req.getAnswers()) || req.getAnswers().size() < 10) {
            return Result.<Boolean>fail().message("提交的答案数量有误");
        }

        List<AnswerReq> answers = req.getAnswers();
        ICommitAnswer answerStrategy = CommitAnswerFactory.getAnswerStrategy(req.getAnswerMode());
        return answerStrategy.execute(rightAnswerCountMap);
    }
}
复制代码

Step5:进一步抽取公共代码,简化代码

不知道大家有没有发现,三个策略中好像都有一段很相似的代码,就是对于正确答案的判断。仔细分析三个策略就可以发现,其实三个策略中不同的地方仅仅是在于对结果的判断,而统计不同难度答对题目的数量操作都是相同的,都是循环比对用户答案与数据库中的答案是否一致,然后进行计数。

既然有公共的地方就可以提取出来,那么提取到哪里比较合适呢?既然三个策略都实现了ICommitAnswer接口,那么不如就将公共代码放入ICommitAnswer接口中去。

public interface ICommitAnswer {

    Result<Boolean> execute(List<AnswerReq> param);

    default Map<Integer,Integer> computerRightCount(List<AnswerReq> param) {
        Map<Integer, Integer> rightAnswerCountMap = new HashMap<>();
        rightAnswerCountMap.put(AnswerStrategyEnum.EASY.getCode(), 0);  // 简单题回答正确的数量
        rightAnswerCountMap.put(AnswerStrategyEnum.MEDIUM.getCode(), 0);  // 中等题回答正确的数量
        rightAnswerCountMap.put(AnswerStrategyEnum.HARD.getCode(), 0);  // 困难题回答正确的数量
        for (AnswerReq answer : param) {
            // 根据题目id获取从数据库中获取正确答案。此步骤略。假定正确答案是A
            String right = "A";

            int addCount = StrUtil.equals(right, answer.getUserAnswer()) ? 1 : 0;
            rightAnswerCountMap.put(answer.getLevel(), 
                                    rightAnswerCountMap.get(answer.getLevel()) + addCount);
        }
        return rightAnswerCountMap;
    }

}
复制代码

现在在接口中添加了computerRightCount方法,并为其添加了默认实现,这个方法就是计算各个难度的题目分别答对了多少题。然后将结果放入一个Map集合中。

@Service
public class ActivityServiceImpl implements ActivityService {

    @Override
    public Result<Boolean> submitAnswers(AnswersSubmitReq req) {
        if (Objects.isNull(req.getAnswers()) || req.getAnswers().size() < 10) {
            return Result.<Boolean>fail().message("提交的答案数量有误");
        }

        List<AnswerReq> answers = req.getAnswers();
        ICommitAnswer answerStrategy = CommitAnswerFactory.getAnswerStrategy(req.getAnswerMode());
        return answerStrategy.execute(answers);
    }
}
复制代码

这样在具体的策略中只需要对正确答案的数量进行判断即可。

public class EasyCommitAnswer implements ICommitAnswer {

    @Override
    public Result<Boolean> execute(List<AnswerReq> answers) {
        Map<Integer, Integer> rightAnswerCountMap = this.computerRightCount(answers);
        if (rightAnswerCountMap.get(AnswerStrategyEnum.EASY.getCode()) >= 6) {
            return Result.<Boolean>success().data(true).message("闯关成功");
        }
        return Result.<Boolean>success().data(false).message("闯关失败");
    }
}
---------------------------------------------------------------------------------------
public class MediumCommitAnswer implements ICommitAnswer {

    @Override
    public Result<Boolean> execute(List<AnswerReq> answers) {
        Map<Integer, Integer> rightAnswerCountMap = this.computerRightCount(answers);
        if (rightAnswerCountMap.get(AnswerStrategyEnum.EASY.getCode()) >= 3
                && rightAnswerCountMap.get(AnswerStrategyEnum.MEDIUM.getCode()) >= 2
                && rightAnswerCountMap.get(AnswerStrategyEnum.HARD.getCode()) >= 1) {
            return Result.<Boolean>success().data(true).message("闯关成功");
        }
        return Result.<Boolean>success().data(false).message("闯关失败");
    }
}
---------------------------------------------------------------------------------------
public class HardCommitAnswer implements ICommitAnswer {

    @Override
    public Result<Boolean> execute(List<AnswerReq> answers) {
        Map<Integer, Integer> rightAnswerCountMap = this.computerRightCount(answers);
        if (rightAnswerCountMap.get(AnswerStrategyEnum.EASY.getCode()) >= 3
                && rightAnswerCountMap.get(AnswerStrategyEnum.MEDIUM.getCode()) >= 2
                && rightAnswerCountMap.get(AnswerStrategyEnum.HARD.getCode()) >= 1) {
            return Result.<Boolean>success().data(true).message("闯关成功");
        }
        return Result.<Boolean>success().data(false).message("闯关失败");
    }
}
复制代码

上述的案例中,并不只有这一个地方可以使用策略模式。抽取题目不也分为几种情况吗,那么这不也可以使用策略模式进行包装吗😄

策略模式实现业务之间的解耦

其实策略模式不仅可以实现上述这种不同业务流程之间的切换,也可以实现不同业务之间的解耦。比如我最近在做的一个项目,我需要对表中的某个字段进行更新,但是更新却分为了几种情况,这几种情况分别散落在不同的业务中。这其实是一件非常恶心的事,因为一个业务中竟然掺杂了对其它业务的处理,如果日后是别人接手了我的代码,那么他看到某个业务中出现了这样一段代码肯定会一脸懵逼。这个地方为什么要对这个字段进行更新?到底还有哪些地方对这个字段进行了操作?所以可维护性就很差。不说别人,可能过段时间过后,我自己都忘了为什么要这么写了。

其实我一开始并没有意识到这个问题,但是通过大佬的一番指点,我采用了策略模式去实现,将对该字段的操作封装成几个策略,然后在不同的业务场景下调用不同的策略。因为一个策略我只调用了一次,所以通过查看这几个策略分别在哪些地方被调用了,我就可以知道有哪些地方对这个字段进行了操作。优点就是代码更加清晰了,维护起来也方便了,同时也避免了多个业务之间的耦合。

总结

其实使用一个设计模式并不一定要完全照搬,因为使用设计模式的目的还是为了代码的整洁、可维护性与可扩展性。所以在使用的时候可以按照自己的使用场景做适当的调整。最重要的还是要理解不同的设计模式到底解决了什么问题,适用于什么场景。当你感觉一段代码写完后看起来感觉比较恶心的时候,就应该思考,是不是可以使用某种设计模式去优化代码。

以上就是我这段时间在项目中使用过策略模式后的一些思考与总结,因为用的不多,所以很多东西说的可能有些片面或者不太正确。有问题欢迎在评论区留言讨论!

### 网络攻击与防护课程的核心概念 网络攻击与防护课程的核心概念主要围绕网络安全的基础理论、常见攻击手段及其防御技术展开。以下是几个关键点: 1. **计算机网络基础** 了解计算机网络的基本原理架构是学习网络攻击与防护的起点。这包括协议栈(如TCP/IP、HTTP)、OSI模型以及数据转发流程等内容[^1]。掌握这些基础知识有助于理解攻击者如何利用网络协议中的漏洞实施攻击。 2. **常见网络攻击类型** 网络攻击的形式多种多样,常见的有SQL注入、XSS(跨站脚本攻击)、CSRF(跨站请求伪造)、DDoS(分布式拒绝服务攻击)等。每种攻击方式都有其特定的技术原理应用场景[^3]。例如,SQL注入通过恶意输入破坏数据库查询逻辑;XSS则利用网页中未过滤的用户输入执行恶意脚本。 3. **网络安全防御机制** 防御机制的设计需要针对不同类型的攻击进行专门化的应对。例如,零信任架构提出了一种全新的安全理念,强调“从不信任,始终验证”,打破了传统基于边界的防护模式,适用于现代复杂网络环境下的安全需求[^2]。此外,建立完善的入侵检测系统(IDS)防火墙规则也是重要的防御手段。 4. **网络攻击链模型** “网络攻击链”模型将攻击过程分为多个阶段,包括侦察目标、武器化、投递载荷等。分析每个阶段可能采用的技术手段,可以帮助安全人员制定更有效的防护策略[^4]。 5. **Web漏洞原理与防御** Web应用作为互联网服务的重要组成部分,其安全性尤为重要。学习Web漏洞(如SQL注入、XSS、CSRF等)的成因及修复方法是课程的重点之一。同时,了解最新的CVE漏洞信息并进行复现实验能够增强实际操作能力。 --- ### 深入学习路径 为了更深入地掌握网络攻击与防护知识,建议按照以下路径逐步推进: #### 1. **夯实基础** - 学习计算机网络基础,熟悉TCP/IP协议栈、OSI七层模型以及数据包的传输过程。 - 掌握Linux系统管理技能,因为大多数服务器运行在Linux平台上,理解操作系统层面的安全配置至关重要[^5]。 #### 2. **研究具体攻击技术** - 系统性学习各类攻击手法,如SQL注入、XSS、CSRF、文件上传漏洞等,并结合实际案例分析其原理。 - 使用工具(如Burp Suite、Metasploit)模拟攻击场景,加深对攻击过程的理解。 #### 3. **探索高级防御技术** - 学习零信任架构的设计思想及其在企业环境中的实现方法。 - 掌握防火墙、IDS/IPS(入侵检测/预防系统)的工作原理及配置技巧。 - 研究加密技术(如SSL/TLS)身份认证机制(如OAuth、JWT),确保通信访问的安全性。 #### 4. **实践与项目经验** - 参与CTF(Capture The Flag)竞赛,锻炼解决真实世界安全问题的能力。 - 构建个人实验室,部署虚拟机或容器环境,测试不同的攻击与防御方案。 - 跟踪最新CVE漏洞信息,尝试复现漏洞并提出修复建议。 #### 5. **持续学习与认证** - 关注网络安全领域的前沿动态,阅读相关论文技术博客。 - 考取专业认证,如CISSP(注册信息系统安全师)、CEH(认证道德黑客)等,提升职业竞争力。 --- ```python # 示例代码:使用Python检测SQL注入风险 import re def is_sql_injection(input_string): # 定义常见的SQL注入关键词正则表达式 sql_keywords = r"(union|select|insert|drop|update|delete|where|from|exec|truncate|char|declare|or|and)" if re.search(sql_keywords, input_string, re.IGNORECASE): return True return False test_input = "SELECT * FROM users WHERE id = '1' OR '1'='1'" if is_sql_injection(test_input): print("可能存在SQL注入风险") else: print("输入安全") ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值