二十三种设计模式[21] - 策略模式(Strategy Pattern)

前言

策略模式,对象行为型模式的一种。在《设计模式 - 可复用的面向对象软件》一书中将之描述为“ 定义一些列的算法,把它们一个个封装起来,并且使它们可相互替换。使得算法可以独立于使用它的客户而变化 ”。

也就是说通过策略模式,我们能够将算法与其调用者分离成相对独立的个体,降低维护成本,使代码更加优雅。

场景

就拿数据的搜索来说,可以简单的分为模糊搜索和精确搜索。在开发这个功能时,可能会写出如下代码。

public List<string> Search(string keyword, SeachModeEnum mode)
{
    //模拟数据
    List<string> dataList = new List<string>
    {
        "abc",
        "Abc",
        "ABc",
        "ABC"
    };

    switch (mode)
    {
        case SeachModeEnum.Exact:
            return dataList.FindAll(t => t == keyword);
        case SeachModeEnum.Fuzzy:
            return dataList.FindAll(t => t.IndexOf(keyword) > -1);
        default:
            return new List<string>();
    }
}

public enum SeachModeEnum
{
    /// <summary>
    /// 精确
    /// </summary>
    Exact,
    /// <summary>
    /// 模糊
    /// </summary>
    Fuzzy
}

上面的代码确实能够解决我们的需求,但这只是一个简单的业务逻辑,如果换成一个复杂的业务算法呢?那么Search函数会变得非常庞大、非常复杂。并且在同一时刻switch中只有一个算法复合当前运行的需求,也就意味着在运行时刻switch中存在着许多与当前业务无关的“垃圾代码”。后续,当我们需要增加新的算法时需要对swtich语句做出修改,随着业务的日渐庞大维护的成本也随之加大。

下面,来看看如何用策略模式实现类似的业务。

结构

  • Strategy(策略接口):用来定义所有算法的公共接口,保证所有算法实现类的一致性;
  • ConcreteStrategy(具体策略):策略接口的实现类,用来实现具体的算法;
  • Context(上下文):定义用户感兴趣的功能。保持Strategy的引用,并将用户的请求转发给Strategy去处理;

在策略模式中,Context依赖于Strategy,Context本身并不实现具体的业务而是将用户的请求转发给具体的Strategy实现。

示例

还是以数据搜索为例,我们首先为每一种搜索方式创建一个实现IStrategy接口的类,以及一个调用各个检索方式的上下文类SearchContext。如下。

public interface IStrategy
{
    SearchContext Context { set; get; }
    List<string> Search(string keyword);
}

/// <summary>
/// 精确搜索
/// </summary>
public class ExactSearchStrategy : IStrategy
{
    public SearchContext Context { set; get; } = null;

    public List<string> Search(string keyword)
    {
        return this.Context.DataList.FindAll(t => t == keyword);
    }
}

/// <summary>
/// 模糊搜索
/// </summary>
public class FuzzySearchStrategy : IStrategy
{
    public SearchContext Context { set; get; } = null;

    public List<string> Search(string keyword)
    {
        return this.Context.DataList.FindAll(t => t.IndexOf(keyword) > -1);
    }
}

public class SearchContext
{
    public SearchContext(IStrategy strategy)
    {
        this.Strategy = strategy;
        this.Strategy.Context = this;
        this.DataList = new List<string>
        {
            "abc","Abc","ABc","ABC"
        };
    }

    private IStrategy Strategy { set; get; } = null;
    /// <summary>
    /// 模拟数据
    /// </summary>
    public List<string> DataList { private set; get; } = new List<string>();

    public List<string> Search(string keyword)
    {
        return this.Strategy.Search(keyword);
    }
}

static void Main(string[] args)
{
    Console.WriteLine("精确搜索:");
    SearchContext context = new SearchContext(new ExactSearchStrategy());
    var dataList = context.Search("AB");
    dataList.ForEach(t => Console.WriteLine(t));
    Console.WriteLine("-------------------------");

    Console.WriteLine("模糊搜索:");
    context = new SearchContext(new FuzzySearchStrategy());
    dataList = context.Search("AB");
    dataList.ForEach(t => Console.WriteLine(t));
    Console.ReadKey();
}

示例中,分别定义了实现IStrategy接口的ExactSearchStrategy和FuzzySearchStrategy策略类来实现模糊搜索和精确搜索业务。同时在这两个策略类中保持了一个SearchContext的引用以便在执行搜索时获取策略类感兴趣的信息(比如示例中的DataList)。当然也可以将这些信息通过参数传递给策略类(比如示例中的keyword)。但并不是所有的策略类都对这些信息感兴趣。保持SearchContext的引用能够使Context避免创建一些策略不感兴趣的数据,但同时也会造成Context与Strategy的循环引用,使其更紧密的耦合。用户在使用SearchContext时将其感兴趣的搜索策略注入到上下文中,再由SearchContext将用户的请求转发给注入的搜索策略类即可实现不同的搜索方式。

在此基础上,我们在增加精确且不区分大小写和模糊且不区分大小写两种搜索方式。如下。

/// <summary>
/// 精确且不区分大小写
/// </summary>
public class ExactAndCaseInsensitiveSearchStrategy : IStrategy
{
    public SearchContext Context { set; get; } = null;

    public List<string> Search(string keyword)
    {
        return this.Context.DataList.FindAll(t => t.ToLower() == keyword.ToLower());
    }
}

/// <summary>
/// 模糊且不区分大小写
/// </summary>
public class FuzzyAndCaseInsensitiveSearchStrategy : IStrategy
{
    public SearchContext Context { set; get; } = null;

    public List<string> Search(string keyword)
    {
        return this.Context.DataList.FindAll(t => t.ToLower().IndexOf(keyword.ToLower()) > -1);
    }
}

static void Main(string[] args)
{
    Console.WriteLine("精确搜索:");
    //SearchContext context = new SearchContext(new ExactSearchStrategy());
    SearchContext context = new SearchContext(new ExactAndCaseInsensitiveSearchStrategy());
    var dataList = context.Search("AB");
    dataList.ForEach(t => Console.WriteLine(t));
    Console.WriteLine("-------------------------");

    Console.WriteLine("模糊搜索:");
    //context = new SearchContext(new FuzzySearchStrategy());
    context = new SearchContext(new FuzzyAndCaseInsensitiveSearchStrategy());
    dataList = context.Search("AB");
    dataList.ForEach(t => Console.WriteLine(t));
    Console.ReadKey();
}

如示例中所示,我们在使用策略模式的过程中可以很轻易的增加或修改使用的算法。只需要增加相应的策略类并修改Context中注入的策略即可增加和修改算法。符合开闭原则。

补充

  • 策略模式 + 单例/享元模式

在策略模式中,当我们只通过参数传递策略感兴趣的数据时这个策略类只存在内部行为而不存在外部状态。所以可以将策略类作为享元共享或者将其设计为一个单例。

策略模式与状态模式的实现很相似。并且都是能够改变类的行为以及减少判断语句的使用。但策略模式的核心在于算法的封装,使用户可以很容易且低成本的变更算法,并且一般情况是这个过程是静态的。而状态模式的核心在于动态的变更对象的行为,在程序的运行过程中由于一些条件的触发,导致上下文(Conatext)中的状态(State)对象发生变更从而改变了上下文的行为。

总结

策略模式可以帮助我们减少条件语句if-else和switch-case的使用。将每种算法单独封装起来即降低了用户对算法的使用、维护以及切换的成本,也易于代码的理解和扩展,复合开闭原则。但策略模式的使用也使得类的数量增多,并且在某些情况下用户需要知道这些策略到底有何不同,此时就不得不向客户暴露策略类具体的实现。

以上,就是我对策略模式的理解,希望对你有所帮助。

示例源码:https://gitee.com/wxingChen/DesignPatternsPractice

系列汇总:https://www.cnblogs.com/wxingchen/p/10031592.html

本文著作权归本人所有,如需转载请标明本文链接(https://www.cnblogs.com/wxingchen/p/10264077.html)

原文地址:https://www.cnblogs.com/wxingchen/p/10264077.html

时间: 2025-01-02 10:21:32

二十三种设计模式[21] - 策略模式(Strategy Pattern)的相关文章

二十四种设计模式:策略模式(Strategy Pattern)

策略模式(Strategy Pattern) 介绍定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换.本模式使得算法的变化可独立于使用它的客户. 示例有一个Message实体类,对它的操作有Insert()和Get()方法,持久化数据在SqlServer数据库中或Xml文件里(两种可互换的算法).由客户端决定使用哪种算法. MessageModel using System; using System.Collections.Generic; using System.Text; na

二十三种设计模式[12] - 代理模式(Proxy Pattern)

前言 代理模式,属于对象结构型模式.在<设计模式 - 可复用的面向对象软件>一书中将之描述为" 为其它对象提供一种代理以控制对这个对象的访问 ". 在代理模式中,通常使用一个类来代表另一个类的功能,并由这个代理对象去控制原对象的引用. 结构 Subjuet(公共接口):代理类和被代理类的公共接口,保证任何使用目标的地方都可以被代理类替换: RealSubject(被代理类):代理类所代表的目标类: Proxy(代理类):包含对目标类的引用,目标类的封装: 场景 在日常生活中

如何让孩子爱上设计模式 ——14.策略模式(Strategy Pattern)

如何让孩子爱上设计模式 --14.策略模式(Strategy Pattern) 描述性文字 本节讲解的是行为型设计模式中的第一个模式: 策略模式, 这个模式非常简单,也很好理解. 定义一系列的算法,把每个算法封装起来,并使得他们可以相互替换, 让算法独立于使用它的客户而变化. 一般用来替换if-else,个人感觉是面向过程与面向对象思想的 过渡,这里举个简易计算器的栗子,帮助理解~ 普通的if-else/switch计算器 普通的面向过程if-else简易计算器代码如下: 运行结果如下: 这里我

二十三种设计模式[20] - 状态模式(State Pattern)

前言 状态模式,对象行为型模式的一种.在<设计模式 - 可复用的面向对象软件>一书中将之描述为" 允许一个对象在其内部状态改变时改变它的行为,使对象看起来似乎修改了它的类 ". 场景 我们都坐过火车,火车可以简单的分为" 开门 "," 关门 "," 运行 "," 停止 "四个状态.火车在这四个状态下分别可以做不同的事情.比如只有在关门时才行运行.只有在停止时才能开门. 我们在开发类似的业务时,往

二十三种设计模式[23] - 访问者模式(Visitor Pattern)

前言 访问者模式,是一种将数据的结构与其操作分离的类行为型模式.它能够帮助我们解决数据结构稳定但数据操作多变的问题,使我们可以很容易的增加或修改数据的操作. 在<设计模式 - 可复用的面向对象软件>一书中将之描述为" 表示一个作用于某对象结构中的各元素的操作.它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作 ". 结构 Visitor(访问者接口):定义了每种元素的访问行为,一般情况下访问行为的个数与元素种类的个数一致: ConcretVisitor(具体访问

二十三种设计模式之原型模式的C#实现

原型模式就是通过拷贝快速创建一个新的对象 本例UML如图 ColorBase [Serializable] public abstract class ColorBase { public int R = 0; public int G = 0; public int B = 0; public virtual ColorBase Clone() { return this; } public virtual void ShowMe() { Console.WriteLine(string.Fo

二十三种设计模式——工厂模式

二十三种设计模式--工厂模式 简单工厂模式 简单工厂模式又称静态工厂方法(StaticFactory Method)模式,不属于23种模式之一. 简单工厂模式是工厂模式最简单使用的模式. 类图: 程序: #include <iostream> #include<string> using namespace std; class NationalFlag//父类 { public: NationalFlag(){} ~NationalFlag(){} virtual void di

设计模式之策略模式(Strategy)摘录

23种GOF设计模式一般分为三大类:创建型模式.结构型模式.行为模式. 创建型模式抽象了实例化过程,它们帮助一个系统独立于如何创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而一个对象创建型模式将实例化委托给另一个对象.创建型模式有两个不断出现的主旋律.第一,它们都将关于该系统使用哪些具体的类的信息封装起来.第二,它们隐藏了这些类的实例是如何被创建和放在一起的.整个系统关于这些对象所知道的是由抽象类所定义的接口.因此,创建型模式在什么被创建,谁创建它,它是怎样被创建的,以

JAVA设计模式之策略模式 - Strategy

在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改.这种类型的设计模式属于行为型模式. 在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象.策略对象改变 context 对象的执行算法. 介绍 什么是策略模式(Strategy Pattern) 在软件开发过程中常常遇到这样的情况, 实现某一个功能有很多种算法或实现策略, 我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能. 如果将这些算法或者策略抽象