zoukankan      html  css  js  c++  java
  • 第23章 行为型模式—策略模式

    1. 策略模式(Strategy Pattern)的定义

    (1)定义:定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。

      ①算法是同一接口的不同实现,地位是平等的,可以相互替换。

      ②引入上下文对象,可以实现让算法能独立使用它的客户。因为这个对象负责持有算法,但不负责决定具体选用哪个算法,把选择算法的功能交给了客户。

      ③当客户通知上下文对象执行功能的时候,上下文会转调具体的算法

    (2)策略模式的结构和说明

     

      ①Strategy:策略接口,用来约束一系列具体的策略算法。Context使用这个接口来调用具体的策略实现定义的算法。

      ②ConcreteStrategy:具体的策略实现,也就是具体的算法实现。

      ③Context:上下文,负责和具体的策略类交互。通常会持有一个真正的策略实现,上下文还可以让具体的策略类来获取上下文的数据,甚至让具体的策略来回调上下文的方法。

    【编程实验】报价管理

     

    //行为型模式——策略模式
    //场景:CRM系统报价策略
    /*
    1.对普通客户或者是新客户报全价
    2.对老客户的价格,统一折扣5%
    3.对大客户报的价格,统一折扣10%
    */
    
    #include <iostream>
    #include <string>
    
    using namespace std;
    
    //策略,定义计算报价算法的接口
    class Strategy
    {
    public:
        //@param goodsPrice 商品销售原价
        //@return 计算出来的,应该给客户报的价格
        virtual double calcPrice(double goodsPrice) = 0;
    };
    
    //新客户或普通客户报价策略
    class NormalCurstomerStrategy : public Strategy
    {
    public:
        double calcPrice(double goodsPrice)
        {
            cout << "对于新客户或者普通客户,没有打折." << endl;
            return goodsPrice;
        }
    };
    
    //老客户计算应报的价格
    class OldCustomerStrategy : public Strategy
    {
    public:
        double calcPrice(double goodsPrice)
        {
            cout << "对于老客户,统一折扣5%" << endl;
            return goodsPrice*(1-0.05);
        }
    
    };
    
    //老客户计算应报的价格
    class LargeCustomerStrategy : public Strategy
    {
    public:
        double calcPrice(double goodsPrice)
        {
            cout << "对于大客户,统一折扣10%" << endl;
            return goodsPrice*(1-0.1);
        }
    
    };
    
    //上下文实现
    class Price
    {
        Strategy* strategy;
    public:
        Price(Strategy* strategy)
        {
            this->strategy = strategy;
        }
    
        //报价,计算对客户的报价(回调相应的打折策略)
        double quote(double goodsPrice)
        {
            return strategy->calcPrice(goodsPrice);
        }
    };
    
    int main()
    {
        //1.选择并创建需要使用的策略对象
        Strategy* strategy = new LargeCustomerStrategy();
        //2.创建上下文
        Price ctx(strategy);
    
        //3.计算报价
        double price = ctx.quote(1000);
    
        cout << "向客户报价:"<< price << endl;
    
        return 0;
    }
    /*输出结果
    对于大客户,统一折扣10%
    向客户报价:900
    */

    2. 思考策略模式

    (1)策略模式的本质分离算法,选择实现

    (2)策略模式的重心:不是如何实现算法,而是如何组织、调用这一系列的算法,从而让程序结构更加灵活,具有更好的维护性和扩展性。

    (3)算法的平等性

      策略模式一个很大的特点就是各个策略算法的平等性。对于一系列具体的策略算法大家的地位是完全一样的,所以才能实现算法之间可以相互替换。可以这样描述策略算法:它是相同行为的不同实现。(如同样是打折这一行为,但商家可以在不同的时间,选择不同的打折策略

    4)谁来选择具体的策略算法

      ①一个地方是在客户端,当使用上下文的时候,由客户端选择具体的策略算法,然后把这个策略算法设置给上下文。

      ②还有一个地方是由上下文选择具体的策略算法。而状态模式可以由具体的状态实现去切换不同的状态。

    (5)Strategy角色的实现方式:可以使用接口或抽象类,特别是当多个算法具有公共功能的时候,可以把Strategy实现成为抽象类。

    (6)运行时策略的唯一性:运行期间,策略模式在每一个时刻只能使用一个具体的策略实现对象,虽然可以动态地在不同策略实现中切换,但是同时只能使用一个。

    (7)Context和Strategy的关系

      ①在策略模式中,通常是上下文使用具体的策略实现对象。反过来,策略实现对象也可以从上下文获取所需要的数据。因此可以将上下文当作参数传递给策略实现对象。

      ②在这种情况下,上下文封装着具体策略对象进行算法运算所需要的数据,具体策略对象通过回调上下文的方法来获取这些数据。

    【编程实验】排序策略的选择

    //行为型模式——策略模式
    //场景:排序算法策略
    
    #include <iostream>
    
    using namespace std;
    
    //策略,定义排序算法策略的接口
    class SortStrategy
    {
    public:
        virtual void sort(int a[],int len) = 0;
    };
    
    //具体的策略:选择排序
    class SelectSort: public SortStrategy
    {
    public:
        void sort(int a[], int len)
        {
            int temp;
            for(int i=0; i< len; i++)
            {
                for (int j=i; j<len; j++)
                {
                    if(a[i] < a[j])
                    {
                        temp = a[i];
                        a[i] = a[j];
                        a[j] = temp;
                    }
                }
            }
        }
    };
    
    //具体策略:冒泡排序
    class BubbleSort: public SortStrategy
    {
    public:
        void sort(int a[], int len)
        {
            int i, j;
            int exchange = 1; //用于优化,如果是己排好序的,就无须再排。
    
            for (i = 0; (i < len)&& exchange; i++)
            {
                exchange = 0;
                for (j = len - 1; j>i;j--)
                {
                    if (a[j]<a[j-1])
                    {
                        int temp = a[j];
                        a[j] = a[j-1];
                        a[j-1] = temp;
                        exchange = 1;
                    }
                }
            }
        }
    };
    
    //具体策略:插入排序
    class InsertSort: public SortStrategy
    {
    public:
        void sort(int a[], int len)
        {
            int i, j, tmp;
            for (i = 1; i <=len - 1;i++)
            {
                tmp = a[i];
                for (j = i - 1; (j >= 0) && (a[j]>tmp); j--)
                {
                    a[j + 1] = a[j];
                    a[j] = tmp;
                }
            }
        }
    };
    
    //上下文类
    class Context
    {
        SortStrategy* sortStrategy;
    public:
        Context(SortStrategy* sortStrategy)
        {
            this->sortStrategy = sortStrategy;
        }
    
        void sort(int array[], int len)
        {
            sortStrategy->sort(array, len);
        }
    };
    
    void show(int a[], int len)
    {
        for (int i = 0; i < len; i++)
        {
            cout << a[i] << " ";
        }
        cout << endl;
    }
    
    int main()
    {
        int a[] = { 49, 38, 65, 97, 76, 13, 49, 27 };
        int len = sizeof(a)/sizeof(a[0]);
        //排序前
    
        cout << "排序前:";
        show(a, len);
    
        //创建具体的排序策略
        SortStrategy* ss = new BubbleSort(); //这里可以更改策略
    
        //创建上下文对象
        Context ctx(ss);
    
        ctx.sort(a, len);
    
        //显示排序后的结果:
        cout << "排序后:";
        show(a, len);
    
        return 0;
    }

    3. 容错恢复机制

    (1)在程序运行的时候,正常情况下应该按照某种方式来做,此时如果发生错误的话,系统并不会崩溃,而是有容忍出错的能力

    (2)不但能容忍程序运行出现错误,还提供出现错误后的备用方案,也就是恢复机制来代替正常执行的功能,使程序继续向下运行。如日志记录,一般会记录在数据库里,但是当数据库暂时连不上时,可先记录在文本文件里,然后在合适的时候再转录到数据库中。

    【编程实验】日志记录策略

     

    //行为型模式——策略模式
    //场景:容错恢复机制(策略模式+模板方法)
    /*
        将日志记录到数据库和文件
    */
    #include <iostream>
    #include <string>
    
    using namespace std;
    
    //日志记录策略的接口
    class LogStrategy
    {
    public:
        //记录日志
        //@param msg 需要记录的日志信息
        virtual void log(string msg) = 0;
        virtual ~LogStrategy(){}
    };
    
    //实现日志策略的抽象模板,实现为消息添加时间
    class LogStrategyTemplate : public LogStrategy
    {
    public:
        void log(string msg)
        {
            //1.增加内容
            string temp;
            temp = "2016-07-04 内容是:" + msg;
    
            //2.真正执行日志记录
            doLog(temp);
        }
    
        virtual void doLog(string msg) = 0;
    };
    
    //实现日志策略接口
    //把日志记录到数据库
    class DbLog : public LogStrategyTemplate
    {
    public:
        void doLog(string msg)
        {
            //制造错误,模仿数据库断开连接
            try
            {
                if(msg.length() >= 30)
                    throw -1;
            }
            catch(...)
            {
                throw -1;
            }
    
            //数据连接正常时,直接写入数据库
            cout << "现在把'" << msg
                 << "'记录到数据库中" << endl;
        }
    };
    
    
    //把日志记录到文件
    class FileLog : public LogStrategyTemplate
    {
    public:
        void doLog(string msg)
        {
            cout << "现在把'" <<msg
                 <<"'记录到文件中" << endl;
        }
    };
    
    //日志记录的上下文
    class LogContext
    {
    private:
        LogStrategy* strategy;
    public:
    
        void log(string msg)
        {
            //在上下文中,自行实现对具体策略的选择
            //优先选用策略:记录到数据库
            try
            {
                strategy = new DbLog();
                strategy->log(msg);
            }
            catch(...)
            {
                //出错了,那就记录到文件中
                strategy =  new FileLog();
                strategy->log(msg);
            }
    
            delete strategy;
        }
    };
    
    int main()
    {
        LogContext log;
        log.log("记录日志");
        log.log("再次记录日志");
    
        system("pause");
    
        return 0;
    }
    /*输出结果:
    现在把'2016-07-04 内容是:记录日志'记录到数据库中
    现在把'2016-07-04 内容是:再次记录日志'记录到文件中
    */

    4. 策略模式的优缺点

    (1)优点

      ①定义一系列算法:策略模式的功能就是定义一系列的算法,实现让这些算法可以相互替换。所以会为这一系列算法定义公共的接口,以约束一系列算法要实现的功能。

      ②避免多重条件语句

      ③更好的扩展性:在策略模式中扩展新的策略实现非常容易,只要增加新的策略实现类,然后在使用策略的地方选择使用这个新的策略实现就可以了。

    (2)缺点

      ①客户必须了解每种策略的不同。比如让客户端来选择具体使用哪一种策略,这就需要客户端了解所有的策略,还要了解各种策略的功能和不同,这样也暴露了策略的具体实现。

      ②增加了对象的数目。如果备选的策略很多的话,那么对象的数目就会很可观。

      ③只适合扁平的算法结构。策略模式的一系列算法地位是平等的,是可以相互替换的,事实上构成了一个扁平的算法结构,也就是在一个策略接口下,有多个平等的策略算法。而且运行的时刻只有一个算法被使用,这就限制了算法使用的层级,使用时不能嵌套使用。如折上折、折后返券等业务的实现,需要组合或嵌套使用多个算法的情况,可以考虑使用装饰模式或变形的责任链来实现。

    5. 策略模式的应用场景

    (1)GUI编程中的布局管理,资源访问策略等。

    (2)出现多个if-else语句来选择行为的时候,可以考虑使用策略模式来代替这些条件语句。

    (3)出现同一个算法,有很多不同的实现的情况下,可以使用策略模式来把这些“不同的实现”实现成一个算法的类层次。

    6. 相关模式

    (1)策略模式和状态模式

      ①状态模式是根据状态的变化来选择相应的行为。其处理的核心问题是状态迁移,因为当存在很多对象的状态下,各个状态之间的跳转和迁移过程是及其复杂的,涉及到多个角色的交互和跳转。在整个生命周期中存在一个状态的迁移曲线,这个迁移曲线对于客户是透明的。状态迁移是状态模式的核心内容;然而,在选择策略时,迁移与此毫无关系

      ②策略模式偏重于可替换的算法,以及这些算法在对应的Context中的正确调用。而状态模式偏重于各状态自身的逻辑运行,其功能类之间是不能相互替换的。如在策略模式中,可能根据不需情况对商品使用不同的打折策略,而状态模式中请假条的审批应先提交项目经理,再提交部门经理,而不能反过来,因固有的逻辑顺序,所以具有不可替换性

      ③状态模式在实现功能的同时,还会维护状态数据的变化及各个状态间的切换。一般在具体的状态实现里会改变Context中的当前状态并调用该状态下相应的方法。这是策略模式和状态模式的明显不同点!!

      ④策略模式允许一个客户选择或提供一种策略,而这种思想在状态模式中是不明显的策略模式行为对象的改变,一般都是外部造成的。而状态模式中对象行为的改变,则即可能是外部造成,也可能是行为对象自身造成的,但通常是自我控制状态的改变

      ⑤Strategy对象一般不会持有自己的数据(因为策略模式封装的只是算法!!),而状态对象则有可能持有自己的数据(stateless型或者state型)

    (2)策略模式和模板方法模式

      ①模板方法重在封装算法骨架

      ②策略模式重在分离并封装算法实现

    (3)策略模式和享元模式

      策略模式分离并封装出一系列的策略算法对象,这些对象的功能通常都比较单一,很多时候就是为了实现某个算法的功能而存在。因此,针对这一系列的、多个细粒度的对象,可以应用享元模式来节省资源。

  • 相关阅读:
    HDU 1584 蜘蛛牌(DFS)
    HDU 1800 Flying to the Mars(贪心)
    zsh: command not found: java (xxx)
    Deepin20安装Mysql8
    Deepin全局菜单
    Ubuntu PPA 解读
    Node安装与配置
    Windows安装配置Maven
    idea 安装 Vue 插件没有Vue component选项
    Linux桌面系统创建应用程序快捷方式
  • 原文地址:https://www.cnblogs.com/5iedu/p/5639626.html
Copyright © 2011-2022 走看看