设计模式-策略模式

示例

策略模式是我们工作中比较常用的一个设计模式,但是初次理解起来可能会有点困难,因此我们还是先看一个例子,假设现在需要开发一个画图工具,画图工具中有钢笔,笔刷和油漆桶,其中,钢笔可以用于描边,但不能填充,笔刷既可以描边,也可以填充,油漆桶只能用于填充,但不能描边。

看到这个需求,最容易想到的可能就是通过继承方式实现了,即钢笔,笔刷和油漆桶都继承自画图工具,然后都实现描边和填充功能,只不过钢笔的填充方法什么都不做,油漆桶的描边方法也什么都不做。(该部分在设计原则中有示例代码,是当时的一个遗留问题)

但是仔细一想,好像哪里不对劲,因为钢笔和油漆桶部分方法不实现,很明显违背了里氏替换原则。而且,正常情况应该是画图工具有用钢笔,笔刷,油漆桶画图的能力,而不是钢笔,笔刷,油漆桶继承自画图工具。因此,我们可以如下实现:

public class Graphics
{
    public void Stroke(ToolEnum tool)
    {
        switch (tool)
        {
            case ToolEnum.Pen:
                Console.WriteLine($"用钢笔描边图形");
                break;
            case ToolEnum.Brush:
                Console.WriteLine($"用笔刷描边图形");
                break;
            case ToolEnum.Bucket:
                Console.WriteLine("油漆桶不能描边图形");
                break;
            default:
                throw new NotSupportedException("不支持的画图工具");
        }
    }

    public void Fill(ToolEnum tool)
    {
        switch (tool)
        {
            case ToolEnum.Pen:
                Console.WriteLine($"钢笔不能填充图形");
                break;
            case ToolEnum.Brush:
                Console.WriteLine($"用笔刷填充图形");
                break;
            case ToolEnum.Bucket:
                Console.WriteLine("用油漆桶填充图形");
                break;
            default:
                throw new NotSupportedException("不支持的画图工具");
        }
    }
}

通过上面枚举的方式,我们实现了让画图工具具备描边和填充的能力,但是这样的switch-case(或者if-else)给扩展和维护都带来了很大的麻烦,而且通过前面对其他设计模式的学习,我相信大家看到这样的代码,一定是不能接受的。起码应该将PenBrushBucket定义成类并且继承自同一个基类,然后组合到Graphics中来,而不是直接使用条件判断,因为用组合代替继承是我们学习设计模式过程中百试不爽的经验。但是,这次好像不怎么灵了,因为,一旦这样做,我们就又回到了原点---钢笔和油漆桶不得不实现不需要的方法。事实上,用组合替代继承是没有错的,但是该怎么组合?组合谁呢?这是个问题。感觉瞬间陷入了两难的局面,这时候策略模式就派上用场了,它不抽象钢笔、笔刷、油漆桶等具体事物,而是直接抽象描边和填充这两种能力,站在代码的角度上看,就是将方法封装成了对象(正应了那句一切皆对象),这正是策略模式最让人费解,但又最妙不可言的地方。我们直接看看用策略模式改进后的代码是怎样的,先抽象能力:

public interface IStrokeStrategy
{
    void Stroke();
}

public class PenStrokeStrategy : IStrokeStrategy
{
    public void Stroke()
    {
        Console.WriteLine($"用钢笔描边图形");
    }
}

public class BrushStrokeStrategy : IStrokeStrategy
{
    public void Stroke()
    {
        Console.WriteLine($"用笔刷描边图形");
    }
}

public interface IFillStrategy
{
    void Fill();
}

public class BrushFillStrategy : IFillStrategy
{
    public void Fill()
    {
        Console.WriteLine($"用笔刷填充图形");
    }
}

public class BucketFillStrategy : IFillStrategy
{
    public void Fill()
    {
        Console.WriteLine("用油漆桶填充图形");
    }
}

看到了吗?直接将StrokeFill两种能力定义成了接口,再通过不同的子类去实现这种能力。然后再看看Graphics类如何组合:

public class Graphics
{
    private IStrokeStrategy _strokeStrategy;
    private IFillStrategy _fillStrategy;

    public Graphics(IStrokeStrategy strokeStrategy,
                    IFillStrategy fillStrategy)
    {
        this._strokeStrategy = strokeStrategy;
        this._fillStrategy = fillStrategy;
    }

    public void Stroke()
    {
        this._strokeStrategy.Stroke();
    }

    public void Fill()
    {
        this._fillStrategy.Fill();
    }
}

画图工具直接拥有了两种能力,但是跟钢笔、笔刷、油漆桶没有直接关系,也就是说,只要给画图工具一个填充的工具,就可以完成填充功能了,至于给的具体是笔刷还是油漆桶,或者其他什么东西,画图工具并不关心。而且,Graphics类中的条件判断语句也都去掉了,隔离了变化,整个类都变得稳定了。
如下就是通过策略模式实现的类图:

定义

再来看一下定义,策略模式定义一系列算法,把他们一个个封装起来,并且使他们可以互相替换。该模式使得算法可以独立于使用它的客户程序而变化。

这里的一系列算法就是不同的描边和填充方式了。不同的描边方式可以相互替换,不同的填充方式也可以相互替换,并且也可以方便的扩展更多的描边和填充方式子类。

UML类图

上面的例子中用到了两组算法,抽象简化之后就得到了如下策略模式的UML类图:

  • Context:策略上下文,持有IStrategy的引用,负责和具体的策略实现交互;
  • IStrategy:策略接口,约束一系列具体的策略算法;
  • ConcreteStrategy:具体的策略实现。

优点

  • 策略可以互相替换;
  • 解决switch-caseif-else带来的难以维护的问题;
  • 策略易于扩展,满足开闭原则.

缺点

  • 客户端必须知道每一个策略类,增加了使用难度。
  • 随着策略的扩展,策略类数量会增多;

第一个缺点无法避免,因为策略模式的一大优点就是算法可以相互替换,但是如果使用者连每个算法代表的是什么意思,优缺点是什么都不知道,又如何替换呢?但第二个缺点却可以通过结合工厂模式,由工厂模式创建具体的策略子类来进行一定程度的缓解,至于具体该怎么实现,这就是工厂模式的知识了,大家可以自行回忆一下。

应用场景

在业务场景中,商家促销活动就非常适合用到策略模式了,因为,商家促销打折可能存在会员折扣,节日折扣,生日折扣等等几十种方式,而且在不同的条件下可以相互替换。
而非业务场景中,日志框架中我们可能会使用log4Net,NLog,Serilog等,而记录位置也可能是控制台,文件,数据库等;系统中的缓存,可以用Redis做分布式缓存,也可以用MemeryCache做本地缓存等,这些场景也都非常适合使用策略模式。

总之,策略模式简约但不简单,学好它妙用无穷!

源码链接

(0)

相关推荐

  • 什么?他居然想在DLL中放毒!

    dotNET跨平台 今天 以下文章来源于My IO ,作者My IO My IO记录工作和生活,将输入变成输出 dotnet/runtime有一个issue[1]讨论了如何使用ModuleInitia ...

  • 设计模式之策略模式

    策略模式 Strategy Intro 策略模式(Strategy):它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化不会影响到使用算法的 Context. 策略模式是一种定 ...

  • 设计模式——策略模式

    什么是策略模式?策略模式属于对象的行为模式.其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换.策略模式使得算法可以在不影响到客户端的情况下发生变化.举个例子? ...

  • PHP设计模式—策略模式

    定义: 策略模式(Strategy):它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户. 结构: Strategy(策略类):定义所有支持的算法的公 ...

  • TypeScript实现设计模式——策略模式

    策略模式(Strategy):它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户. --<大话设计模式> 策略模式主要用来解决当有多种相似算 ...

  • PHP设计模式之策略模式

    PHP设计模式之策略模式 策略模式,又称为政策模式,属于行为型的设计模式. Gof类图及解释 GoF定义:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换.本模式使得算法可独立于使用它的 ...

  • [PHP小课堂]PHP设计模式之策略模式

    [PHP小课堂]PHP设计模式之策略模式 关注公众号:[硬核项目经理]获取最新文章 添加微信/QQ好友:[DarkMatterZyCoder/149844827]免费得PHP.项目管理学习资料

  • 用工厂设计模式+策略什么模式,彻底干掉if else

    老哥哔哔叨 无论是人生还是代码,都面临着很多的选择,代码里面充斥着无数的if / else,人生不也是一样吗?无数的分岔路口,我们无法避免这些选择,但是我们可以更优美的去做选择-设计模式. 谈恋爱的烦 ...

  • 设计模式(22) 策略模式

    在策略模式中,一个类的行为或算法可以在运行时动态更改. GOF对策略模式的描述为: Define a family of algorithms, encapsulate each one, and m ...

  • 详解JAVA面向对象的设计模式 (二)、策略模式

    策略模式 Strategy 介绍 在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改.这种类型的设计模式属于行为型模式. 在策略模式中,我们创建表示各种策略的对象和 ...