勇哥谈谈策略模式(Strategy Pattern)

勇哥注:

策略模式和状态模式是两个亲兄弟,类图很像。

策略模式是封装了一系列算法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同。就像是同样是亿万富豪,马云是靠卖东西,王思聪是靠继承。

状态模式的每种状态做的事,可以完全不同。


image.png

按类图编写的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication20
{
    class Program
    {
        static void Main(string[] args)
        {
            Context c = new Context(new ConcreteStrategyB());
            c.ContextInterface();

            c = new Context(new ConcreteStrategyA());
            c.ContextInterface();

            Console.ReadKey();
        }
    }

    public abstract class Strategy
    {
        public abstract void Algorithmlnterface();
    }

    public class ConcreteStrategyA : Strategy
    {
        public override void Algorithmlnterface()
        {
            Console.WriteLine(@"算法A");
        }
    }

    public class ConcreteStrategyB : Strategy
    {
        public override void Algorithmlnterface()
        {
            Console.WriteLine(@"算法B");
        }
    }

    public class ConcreteStrategyC : Strategy
    {
        public override void Algorithmlnterface()
        {
            Console.WriteLine(@"算法C");
        }
    }



    public class Context
    {
        private Strategy strategy;

        public Context(Strategy s)
        {
            this.strategy = s;
        }

        public void ContextInterface()
        {
            strategy.Algorithmlnterface();
        }

    }



}



何时使用策略模式

阿里开发规约-编程规约-控制语句-第六条 :

超过 3 层的 if-else 的逻辑判断代码可以使用卫语句、策略模式、状态模式等来实现。

相信大家都见过这种代码:

if (conditionA) {
    逻辑1
} else if (conditionB) {
    逻辑2
} else if (conditionC) {
    逻辑3
} else {
    逻辑4
}

这种代码虽然写起来简单,但是很明显违反了面向对象的 2 个基本原则:

单一职责原则(一个类应该只有一个发生变化的原因):因为之后修改任何一个逻辑,当前类都会被修改

开闭原则(对扩展开放,对修改关闭):如果此时需要添加(删除)某个逻辑,那么不可避免的要修改原来的代码


因为违反了以上两个原则,尤其是当 if-else 块中的代码量比较大时,

后续代码的扩展和维护就会逐渐变得非常困难且容易出错,

使用卫语句也同样避免不了以上两个问题。


因此根据我的经验,得出一个我个人认为比较好的实践:


if-else 不超过 2 层,块中代码 1~5 行,直接写到块中,否则封装为方法

if-else 超过 2 层,但块中的代码不超过 3 行,尽量使用卫语句

if-else 超过 2 层,且块中代码超过 3 行,尽量使用策略模式



例子:商场打折


代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 商场打折
{
    class Program
    {
        static void Main(string[] args)
        {
            Context c = new Context();
            c.Set算法(new 打8折());
            Console.WriteLine( c.CalResult(100,2));
            Console.ReadKey();
        }
    }

    public class Context
    {
        private 算法 sf;
        public void Set算法(算法 s)
        {
            this.sf = s;
        }

        public double CalResult(double numa,double numb)
        {
            this.sf.单价 = numa;
            this.sf.数量 = numb;
           return  this.sf.CalResult();
        }
    }

    public abstract class 算法
    {
        public double 单价 { get; set; }

        public double 数量 { get; set; }
        public abstract double CalResult();
    }

    public class 正常收费 : 算法
    {
        public override double CalResult()
        {
            return 单价 * 数量;
        }
    }

    public class 满300返100 : 算法
    {
        public override double CalResult()
        {
            var s = 单价 * 数量;
            return Math.Floor(s / 300) * 100 + s;
        }
    }

    public class 打8折 : 算法
    {
        public override double CalResult()
        {
            return (单价 * 数量) * 0.8;
        }
    }

}


例子:临时工工资计算

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 电工临时工工资
{
    /*
    高级工:时薪为25块/小时
    中级工:时薪为20块/小时
    初级工:时薪为15块/小时
    按每天10小时的工作时长来算。
        */
    class Program
    {
        static void Main(string[] args)
        {
  Context c = new Context(new Level1());
            
            Console.WriteLine( c.CalGZ());

            Console.ReadKey();

            Console.ReadKey();
        }
    }
    
   public class Context
       {
        private CalGZMethod method;

        public Context(CalGZMethod m)
        {
            this.method = m;
        }

        public double CalGZ()
        {
            return method.CalGz();
        }
       }

    public abstract class CalGZMethod
    {
        public abstract double CalGz();
    }

    public class Level3 : CalGZMethod
    {
        public override double CalGz()
        {
            return 25 * 10*28;
        }
    }

    public class Level2 : CalGZMethod
    {
        public override double CalGz()
        {
            return 20 * 10*28;
        }
    }

    public class Level1 : CalGZMethod
    {
        public override double CalGz()
        {
            return 15 * 10 * 28;
        }
    }



}


例子:使用字典+Lambda表达式优化简单逻辑的if else


杀鸡焉用宰牛刀?就是几个if else场景我需要用到策略模式?

因此简单的if else可以使用字典+Lambda表达式方式实现函数映射,来实现。


还是上面的例子改造一下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 电工临时工工资
{
    /*
    高级工:时薪为25块/小时
    中级工:时薪为20块/小时
    初级工:时薪为15块/小时
    按每天10小时的工作时长来算。
        */
    class Program
    {

        public enum WorkerEnum
        {
            高级工, 中级工, 初级工
        }

        static void Main(string[] args)
        {
                     CalGZMethod fun;
                     var p1 = new Level1();
                     Console.WriteLine( p1.CalGz());

            Console.ReadKey();

            Dictionary<WorkerEnum, Func<double>> map = new Dictionary<WorkerEnum, Func< double>>();
            map.Add(WorkerEnum.高级工, () => { return 25 * 10 * 28; });
            map.Add(WorkerEnum.中级工, () => { return 20 * 10 * 28; });
            map.Add(WorkerEnum.初级工, () => { return 15 * 10 * 28; });

            var result = map[WorkerEnum.高级工];
            if(result!=null)
            {
                Console.WriteLine(result.Invoke());
            }

            Console.ReadKey();
        }
    }

    public class Context
    {
        private CalGZMethod method;

        public Context(CalGZMethod m)
        {
            this.method = m;
        }

        public double CalGZ()
        {
            return method.CalGz();
        }
    }    
    
    
    public abstract class CalGZMethod
    {
        public abstract double CalGz();
    }

    public class Level3 : CalGZMethod
    {
        public override double CalGz()
        {
            return 25 * 10*28;
        }
    }

    public class Level2 : CalGZMethod
    {
        public override double CalGz()
        {
            return 20 * 10*28;
        }
    }

    public class Level1 : CalGZMethod
    {
        public override double CalGz()
        {
            return 15 * 10 * 28;
        }
    }



}


如果有人说,代码都写在Lambda表达式中,那太长了怎么办?

这个时候我们可以把继承CalGZMethod的三个类用起来。

这个时候实际上是用字典把Context类的功能给替代了。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 电工临时工工资
{
    /*
    高级工:时薪为25块/小时
    中级工:时薪为20块/小时
    初级工:时薪为15块/小时
    按每天10小时的工作时长来算。
        */
    class Program
    {

        public enum WorkerEnum
        {
            高级工, 中级工, 初级工
        }

        static void Main(string[] args)
        {
            CalGZMethod fun;
            var p1 = new Level1();
            Console.WriteLine( p1.CalGz());

            Console.ReadKey();

            Dictionary<WorkerEnum, Func<double>> map = new Dictionary<WorkerEnum, Func< double>>();
            //map.Add(WorkerEnum.高级工, () => { return 25 * 10 * 28; });
            //map.Add(WorkerEnum.中级工, () => { return 20 * 10 * 28; });
            //map.Add(WorkerEnum.初级工, () => { return 15 * 10 * 28; });

            map.Add(WorkerEnum.高级工, (new Level3()).CalGz);
            map.Add(WorkerEnum.中级工, (new Level2()).CalGz);
            map.Add(WorkerEnum.初级工, (new Level1()).CalGz);


            var result = map[WorkerEnum.高级工];
            if(result!=null)
            {
                Console.WriteLine(result.Invoke());
            }

            Console.ReadKey();
        }
    }

    public interface MyFunction
    {
        void apply();
    }

    public abstract class CalGZMethod
    {
        public abstract double CalGz();
    }

    public class Level3 : CalGZMethod
    {
        public override double CalGz()
        {
            return 25 * 10*28;
        }
    }

    public class Level2 : CalGZMethod
    {
        public override double CalGz()
        {
            return 20 * 10*28;
        }
    }

    public class Level1 : CalGZMethod
    {
        public override double CalGz()
        {
            return 15 * 10 * 28;
        }
    }



}



策略模式优点讨论:

策略模式是一种定义了一系列算法的模式,从概念上来看,

所有这些算法完成的都是相同的工作,只是实现不同。

它可以以相同的方式调用所有算法,减少了各种算法类与使用算法类之间的耦合[DPE]。

策略模式的Strategy类层次为Context定义了一系列的可供重用的算法或行为。

继承有助于提取出这些算法中的公共功能[DP]。

另一个策略模式的优点是简化了单元测试,因为每个算法都有自己的类,

可以通过自己的接口单独测试[DPE]


状态模式和策略模式对比:

状态模式和策略模式是很相近的,包括他们的代码和要解决的问题都相差不大,

它们都封装了一系列的算法或者行为,他们的类图看起来几乎如出一辙,

可是从意图上看它们有很大不一样。

相同点:都有一个上下文、一些策略类或者状态类,上下文把请求委托给这些类来执行

区别:策略模式中的各个策略类之间是平等又平行的,它们之间没有任何关系,

因此客户必须熟知这些策略类的做用,以便客户本身能够随时主动切换算法。

可是在状态模式中,状态和状态对应的行为早已被封装好,状态之间的切换也早就被规定,

“改变行为”这件事发生在状态模式的内部,对于客户来讲,不需要了解其中的细节。




--------------------- 

作者:hackpig

来源:www.skcircle.com

版权声明:本文为博主原创文章,转载请附上博文链接!



本文出自勇哥的网站《少有人走的路》wwww.skcircle.com,转载请注明出处!讨论可扫码加群:
本帖最后由 勇哥,很想停止 于 2023-11-28 14:37:49 编辑

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

会员中心
搜索
«    2024年4月    »
1234567
891011121314
15161718192021
22232425262728
2930
网站分类
标签列表
最新留言
    热门文章 | 热评文章 | 随机文章
文章归档
友情链接
  • 订阅本站的 RSS 2.0 新闻聚合
  • 扫描加本站机器视觉QQ群,验证答案为:halcon勇哥的机器视觉
  • 点击查阅微信群二维码
  • 扫描加勇哥的非标自动化群,验证答案:C#/C++/VB勇哥的非标自动化群
  • 扫描加站长微信:站长微信:abc496103864
  • 扫描加站长QQ:
  • 扫描赞赏本站:
  • 留言板:

Powered By Z-BlogPHP 1.7.2

Copyright Your skcircle.com Rights Reserved.

鄂ICP备18008319号


站长QQ:496103864 微信:abc496103864