Skip to content
快速预览

策略模式(下)--案例

✍️ w 🕒 2023-08-01 14:34:55(10 months ago) 🔗 G.设计模式

策略模式,重点是先找到能够进行策略划分的重点,然后将其一部分作为策略点进行分类,也不用非要强行去进行策略,在合理阶段时机进行就可以。

java 案例一

在第一讲的时候使用 创建一个类的然后这类会收集职责和判断条作为绑定,来解决一些复杂判断情况,这个案例将使用将条件和职责维护一起

java
public interface TravelStrategy {
    void travel();
    boolean isSuitable(Condition condition);
}

// 判断条件的对象
public class Condition {
    private String weather;
    private String traffic;
    private int distance;

    // getters and setters
}

// 实现接口的策略
public class BikeTravelStrategy implements TravelStrategy {
    @Override
    public void travel() {
        System.out.println("Travel by bike.");
    }

    @Override
    public boolean isSuitable(Condition condition) {
        return "sunny".equals(condition.getWeather()) 
               && "light".equals(condition.getTraffic()) 
               && condition.getDistance() < 5;
    }
}

// implement `isSuitable` for other strategies in the same way


// 上下文信息类(Context)
public class TravelContext {
    private List<TravelStrategy> strategies = new ArrayList<>();
    private TravelStrategy travelStrategy;

    public TravelContext() {
        strategies.add(new BikeTravelStrategy());
        strategies.add(new CarTravelStrategy());
        strategies.add(new WalkTravelStrategy());
    }

		// 使用的时候先注入 判读条件根据条件选出符合的策略
    public void setCondition(Condition condition) {
        for (TravelStrategy strategy : strategies) {
            if (strategy.isSuitable(condition)) {
                travelStrategy = strategy;
                return;
            }
        }
        throw new IllegalArgumentException("No suitable strategy found for condition: " + condition);
    }

		// 使用策略
    public void travel() {
        if (travelStrategy != null) {
            travelStrategy.travel();
        } else {
            throw new IllegalStateException("Travel strategy is not set.");
        }
    }
}

// 实际使用
public class Main {
    public static void main(String[] args) {
        TravelContext travelContext = new TravelContext();
        Condition condition = new Condition();

        condition.setWeather("sunny");
        condition.setTraffic("light");
        condition.setDistance(3);
        travelContext.setCondition(condition);
        travelContext.travel();  // Output: Travel by bike.

        condition.setWeather("rainy");
        condition.setTraffic("heavy");
        condition.setDistance(10);
        travelContext.setCondition(condition);
        travelContext.travel();  // Output: Travel by car.

        condition.setWeather("windy");
        condition.setTraffic("light");
        condition.setDistance(1);
        travelContext.setCondition(condition);
        travelContext.travel();  // Output: Travel by walking.
    }
}

java 案例二

现在 有三种组合状态组合对价格优惠,例如 第一个按钮是9 折 第二个按钮是 减10元 第三个按钮是 优惠20

java
public interface DiscountStrategy {
    double apply(double price);
}

public class DiscountNine implements DiscountStrategy {
    @Override
    public double apply(double price) {
        return price * 0.9;
    }
}

public class DiscountTen implements DiscountStrategy {
    @Override
    public double apply(double price) {
        return price - 10;
    }
}

public class DiscountTwenty implements DiscountStrategy {
    @Override
    public double apply(double price) {
        return price - 20;
    }
}

public class PriceCalculator {
    private List<DiscountStrategy> strategies = new ArrayList<>();

    public void addStrategy(DiscountStrategy strategy) {
        strategies.add(strategy);
    }

    public double calculate(double price) {
        for (DiscountStrategy strategy : strategies) {
            price = strategy.apply(price);
        }
        return price;
    }
}

// 使用
public static void main(String[] args) {
    PriceCalculator calculator = new PriceCalculator();
    calculator.addStrategy(new DiscountNine());
    calculator.addStrategy(new DiscountTen());
    System.out.println(calculator.calculate(100));  // 输出: 71.0
}

js 案例一

上面同样的问题 js 的解决方法

html
<html>
<head>
    <meta charset="utf-8">
</head>
<body>
<form action="" id="registerForm" method="post">
    请输入用户名:<input type="text" name="userName"/>
    请输入密码:<input type="text" name="passWord"/>
    请输入手机号码:<input type="text" name="phoneNumber"/>
    <input type="submit" value="提交"/>
</form>
<script>
    /***************策略对象*************/
    const strategies = {
        isNotEmpty(value, errorMsg) {
            if(value === ''){
                return errorMsg;
            }
        },
        minLength(value, length, errorMsg) {
            if(value.length < length){
                return errorMsg;
            }
        },
        isMobile(value, errorMsg) {
            if(!/(^1[3|5|8][0-9]{9}$)/.test(value)){
                return errorMsg;
            }
        }
    }
    /***************validator类***************/
    class Validator {
        constructor() {
            this.cache = [];
        }

        add(dom, rules) {
            for(let i = 0; i < rules.length; i++) {
                let rule = rules[i];
                let strategyAry = rule.strategy.split(':');
                let errorMsg = rule.errorMsg;

                this.cache.push(() => {
                    let strategy = strategyAry.shift();
                    strategyAry.unshift(dom.value);
                    strategyAry.push(errorMsg);

                    return strategies[strategy].apply(dom, strategyAry);
                });
            }
        }

        start() {
            for(let i = 0; i < this.cache.length; i++) {
                let validatorFunc = this.cache[i];
                let errorMsg = validatorFunc();
                if(errorMsg){
                    return errorMsg;
                }
            }
        }
    }

    /************客户调用代码***********/
    const registerForm = document.getElementById('registerForm');

    const validatorFunc = () => {
        const validator = new Validator();

        validator.add(registerForm.userName,[{
            strategy:'isNotEmpty',
            errorMsg:'用户名不能为空'
        },{strategy:'minLength:10',
            errorMsg:'用户名长度不能小于10位'
        }]);

        validator.add(registerForm.passWord,[{
            strategy:'minLength:6',
            errorMsg:'用户密码不能小于6位'
        }]);
        validator.add(registerForm.phoneNumber,[{
            strategy:'isMobile',
            errorMsg:'手机号码格式不正确'
        }]);

        const errorMsg = validator.start();
        return errorMsg;
    }

    registerForm.onsubmit = () => {
        const errorMsg = validatorFunc();
        if(errorMsg){
            alert(errorMsg);
            return false;
        }
    }
</script>
</body>
</html>

js 案例二

表单验证,通过if else来进行判断验证,后期会维护大量的if - else的逻辑验证

html
<body>
<form id='login-form' action="" method="post">
    <label for="userName">用户名</label>
    <input type="text" id="userName" name="userName">
    <label for="phoneNumber">手机号</label>
    <input type="number" id="phoneNumber" name="phoneNumber">
    <label for="password">密码</label>
    <input type="password" id="password" name="password">
    <button id='login'>登录</button>
</form>
</body>
<script>
    var loginForm = document.getElementById('login-form');
    loginForm.onsubmit = function () {
        if(loginForm.userName.value ===''){
            alert('用户名不能为空')
            return false
        }
        if(loginForm.password.value.length<6){
            alert('密码长度不能小于六位')
            return false;
        }
        if (!/(^1[3|4|5|7|8][0-9]{9}$)/.test(loginForm.phoneNumber.value)) {
            alert('手机号格式错误')
            return false;
        }
    }

</script>
  • 利用策略模式要将代码拆成两个两部分,第一个部分'封装不同策略的策略组',也就是要将这些验证对象封装成策略对象,第二部分需要'Context'执行这些策略的调用,调用策略对象

第一部分要封装的策略组代码,也就是上面的if else,都需要被判断参数,报错信息。其中判断长度比较特别需要,一个额外的长度来控制各种长度出现的情况

二部分也就是一个'执行这些策略的调用',构想就是分成两部分,第一部分就是添加你想要的策略,第二部分就是统一执行你添加的策略

根据上面分析,整个负责验证的策略对象中的每个策略,最少需要两个参数,第一个参数就是要验证的值,第二个参数就是验证后的报错提示信息。除了特殊策略,例如长度验证需要一个关于长度的判断值

  • es5 版本
html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form  id="registerForm" method="post">
        <label for="userName">用户名</label>
        <input type="text" id="userName" name="userName">
        <label for="phoneNumber">手机号</label>
        <input type="number" id="phoneNumber" name="phoneNumber">
        <label for="password">密码</label>
        <input type="password" id="password" name="password">
        <button id='login'>登录</button>
    </form>
</body>
<script>
    /**
     * 策略模式的两个点
     * 1.存所有的验证(策略)
     * 2.有一个可以调用所有验证(执行策略)
     * 策略模式执行的类:
     * 3.有一个方法这个方法是专门又来存这些验证的,存这些验证需要什么
     *  3.1 需要一个能存的变量 因为要存多个这个变量选数组
     *  3.2 要存的是什么,这里要存的是错误提示
     *      3.2.1 要存错误提示怎么存是进来一个就调用把错误提示存进这个变量?
     *            还是说存这些方法,在最后执行的时候调用这些方法比较好
     *            这里选用后者存这些验证方法
     *  3.3 能存了核心在哪,在策略模式最后需要有个执行者,执行这些策略,也就是
     *      这个类需要一个执行方法start
     *  4.要细分存储策略的add方法的具体参数,需要有一个要验证的对象,需要有验证的规则,需要有验证的提示
     * **/

    // 策略对象
    const strategies = {
        isNonEmpty: function (value, errorMsg) { // 不能为空,第一个参数要验证的值,第二个是提示
            if(value === ''){
                return errorMsg
            }
        },
        minLength:function (value, length,errorMsg) { // 最小长度
            if(value.length < length){
                return errorMsg
            }
        },
        isMobile:function (value,errorMsg) {
            if (!/(^1[3|4|5|7|8][0-9]{9}$)/.test(value)) { // 验证电话号码
                return errorMsg
            }
        }
    }

    // 委托来让 Context 拥有执行策略的能力Validator
    function Validator() {
        // 对应第三条的分析
        this.cache = []
    }
    // 关于参数对照第四条
    Validator.prototype.add = function (dom,rule,errorMsg) {
        // 规则针对要传入自己额外规则参数的验证 我们规定用':' 分开
        // 因此需要对规则在进行拆分
        var ary = rule.split(':')
        // 对规则进一步的验证发方法存放在this.cache中
        this.cache.push(function () {
            // 验证规则
            var strategy = ary.shift()
            // 分析我么的策略中最特殊需要参数的方法function (value, length,errorMsg)
            // 经过上一步的操作 ary 中只能有两种情况,一种是空数组,一种是有一个自己定义的验证判断规则参数
            ary.unshift(dom.value)
            ary.push(errorMsg)
            console.log(strategy)
            return strategies[strategy].apply(dom,ary)
        })
    }
    // 最后调用策略的方法
    Validator.prototype.start = function () {
        for (var i=0,validatorFunc;validatorFunc = this.cache[i++];){
            var msg = validatorFunc()
            if(msg){
                return msg
            }
        }
    }

    var registerForm = document.getElementById('registerForm')

    var validataFun = function () {
        var validator = new Validator()
        validator.add(registerForm.userName,'isNonEmpty','用户名不能为空')
        validator.add(registerForm.password,'minLength:6','密码长度不能少于六位')
        validator.add(registerForm.phoneNumber,'isMobile','手机格式不正确')
        var errorMsg = validator.start()
        return errorMsg
    }
    registerForm.onsubmit = function (e) {
                e.preventDefault()

        var errorMsg = validataFun()

        if(errorMsg){
            alert(errorMsg)
            return false // 组织表单提交
        }
    }

</script>
</html>
  • es6 思路
html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <label for="userName">用户名</label>
    <input type="text" id="userName" name="userName">
    <label for="phoneNumber">手机号</label>
    <input type="number" id="phoneNumber" name="phoneNumber">
    <label for="password">密码</label>
    <input type="password" id="password" name="password">
    <button id='login'>登录</button>

</body>
<script>
    // 策略对象
    const strategies = {
        isNonEmpty: function (value, errorMsg) { // 不能为空,第一个参数要验证的值,第二个是提示
            if(value === ''){
                return errorMsg
            }
        },
        minLength:function (value, length,errorMsg) { // 最小长度
            if(value.length < length){
                return errorMsg
            }
        },
        isMobile:function (value,errorMsg) {
            if (!/(^1[3|4|5|7|8][0-9]{9}$)/.test(value)) { // 验证电话号码
                return errorMsg
            }
        }
    }

    // 执行这些策略的调用的类Validator -- 这里直接es6 写法
    class Validator{
        constructor(strategies){
            this.cache = []
            this.strategies = strategies // 策略对象
        }


        // 保存所有验证方法
        add(value, rule, errorMsg){
            let ary = rule.split(':')
            this.cache.push(()=>{
                let strategy = ary.shift()
                ary.unshift(value)
                ary.push(errorMsg)
                // 当想给函数传多个参数利用数组的形式的话es5 首先考虑利用apply
                // es6 理解结构赋值的方法this.strategies[strategy](...ary)
                return this.strategies[strategy].apply(this,ary)
            })
        }

        // 统一执行调用
        start(){
            let msg = ''
            for(let item of this.cache){
                msg = item()
                if(msg){
                    return msg
                }
            }
        }

    }

    let userName = document.getElementById('userName');
    let password = document.getElementById('password');
    let phoneNumber = document.getElementById('phoneNumber');

    // 每次执行调用为了获取最新的输入value值
    let validataFun = function () {
        // 创建一个'执行这些策略的调用' 的Validator实例
        let validator = new Validator(strategies)
        validator.add(userName.value,'isNonEmpty','用户名不能为空')
        validator.add(password.value,'minLength:6','密码长度不能少于六位')
        validator.add(phoneNumber.value,'isMobile','手机格式不正确')
        var errorMsg = validator.start()
        return errorMsg
    }


    document.getElementById('login').onclick = ()=>{
       let errorMsg = validataFun()
        if(errorMsg){
            alert(errorMsg)
        }
    }


</script>
</html>

上面是单个验证 如果多个验证验证代码解决思路

html
<html>
<head>
    <meta charset="utf-8">
</head>
<body>
<form action="" id="registerForm" method="post">
    请输入用户名:<input type="text" name="userName"/>
    请输入密码:<input type="text" name="passWord"/>
    请输入手机号码:<input type="text" name="phoneNumber"/>
    <input type="submit" value="提交"/>
</form>
<script>
    /***************策略对象*************/
    var strategies = {
        isNotEmpty:function(value,errorMsg){
            if(value === ''){
                return errorMsg;
            }
        },
        minLength:function(value,length,errorMsg){
            if(value.length<length){
                return errorMsg;
            }
        },
        isMobile:function(){
            if(!/(^1[3|5|8][0-9]{9}$)/.test(value)){
                return errorMsg;
            }
        }
    }
    /***************validator类***************/
    var  Validator = function(){
        this.cache = [];
    };

    Validator.prototype.add = function(dom,rules){
        var self = this;

        for(var i=0,rule;rule = rules[i++];){
            (function(rule){
                var strategyAry = rule.strategy.split(':');
                var errorMsg = rule.errorMsg;

                self.cache.push(function(){
                    var strategy = strategyAry.shift();
                    strategyAry.unshift(dom.value);
                    strategyAry.push(errorMsg);

                    return strategies[strategy].apply(dom,strategyAry);
                })
            })(rule);
        }
    }
    Validator.prototype.start = function(){
        for(var i=0,validatorFunc;validatorFunc = this.cache[i++];){
            var errorMsg = validatorFunc();
            if(errorMsg){
                return errorMsg;
            }
        }
    }

    /************客户调用代码***********/
    var registerForm = document.getElementById('registerForm');

    var validatorFunc = function(){
        var validator = new Validator();

        validator.add(registerForm.userName,[{
            strategy:'isNotEmpty',
            errorMsg:'用户名不能为空'
        },{strategy:'minLength:10',
            errorMsg:'用户名长度不能小于10位'
        }]);

        validator.add(registerForm.passWord,[{
            strategy:'minLength:6',
            errorMsg:'用户密码不能小于6位'
        }]);
        validator.add(registerForm.phoneNumber,[{
            strategy:'isMobile',
            errorMsg:'手机号码格式不正确'
        }]);

        var errorMsg = validator.start();
        return errorMsg;
    }

    registerForm.onsubmit = function(){
        var errorMsg = validatorFunc();
        if(errorMsg){
            alert(errorMsg);
            return false;
        }
    }
</script>
</body>
</html>

es6 写法

html
<html>
<head>
    <meta charset="utf-8">
</head>
<body>
<form action="" id="registerForm" method="post">
    请输入用户名:<input type="text" name="userName"/>
    请输入密码:<input type="text" name="passWord"/>
    请输入手机号码:<input type="text" name="phoneNumber"/>
    <input type="submit" value="提交"/>
</form>
<script>
    /***************策略对象*************/
    const strategies = {
        isNotEmpty(value, errorMsg) {
            if(value === ''){
                return errorMsg;
            }
        },
        minLength(value, length, errorMsg) {
            if(value.length < length){
                return errorMsg;
            }
        },
        isMobile(value, errorMsg) {
            if(!/(^1[3|5|8][0-9]{9}$)/.test(value)){
                return errorMsg;
            }
        }
    }
    /***************validator类***************/
    class Validator {
        constructor() {
            this.cache = [];
        }

        add(dom, rules) {
            for(let i = 0; i < rules.length; i++) {
                let rule = rules[i];
                let strategyAry = rule.strategy.split(':');
                let errorMsg = rule.errorMsg;

                this.cache.push(() => {
                    let strategy = strategyAry.shift();
                    strategyAry.unshift(dom.value);
                    strategyAry.push(errorMsg);

                    return strategies[strategy].apply(dom, strategyAry);
                });
            }
        }

        start() {
            for(let i = 0; i < this.cache.length; i++) {
                let validatorFunc = this.cache[i];
                let errorMsg = validatorFunc();
                if(errorMsg){
                    return errorMsg;
                }
            }
        }
    }

    /************客户调用代码***********/
    const registerForm = document.getElementById('registerForm');

    const validatorFunc = () => {
        const validator = new Validator();

        validator.add(registerForm.userName,[{
            strategy:'isNotEmpty',
            errorMsg:'用户名不能为空'
        },{strategy:'minLength:10',
            errorMsg:'用户名长度不能小于10位'
        }]);

        validator.add(registerForm.passWord,[{
            strategy:'minLength:6',
            errorMsg:'用户密码不能小于6位'
        }]);
        validator.add(registerForm.phoneNumber,[{
            strategy:'isMobile',
            errorMsg:'手机号码格式不正确'
        }]);

        const errorMsg = validator.start();
        return errorMsg;
    }

    registerForm.onsubmit = () => {
        const errorMsg = validatorFunc();
        if(errorMsg){
            alert(errorMsg);
            return false;
        }
    }
</script>
</body>
</html>

Released under the MIT License.