设计模式的艺术之道--策略模式

本文通过一个电影票打折系统案例,详细介绍了策略模式的概念、结构及应用。策略模式将一系列算法封装成独立的对象,使得算法的选择更加灵活,易于扩展。

设计模式的艺术之道–策略模式

声明:本系列为刘伟老师博客内容总结(http://blog.youkuaiyun.com/lovelion),博客中有完整的设计模式的相关博文,以及作者的出版书籍推荐

本系列内容思路分析借鉴了刘伟老师的博文内容,同时改用C#代码进行代码的演示和分析(Java资料过多 C#表示默哀).

本系列全部源码均在文末地址给出。


本系列开始讲解行为型模式,关注如何将现有类或对象组织在一起形成更加强大的结构。

  • 行为型模式(Behavioral Pattern)
    关注系统中对象之间的交互,研究系统在运行时对象之间的相互通信与协作,进一步明确对象的职责
    不仅仅关注类和对象本身,还重点关注它们之间的相互作用和职责划分
  • 类行为型模式
    使用继承关系在几个类之间分配行为,主要通过多态等方式来分配父类与子类的职责
  • 对象行为型模式
    使用对象的关联关系来分配行为,主要通过对象关联等方式来分配两个或多个类的职责

11种常见的行为型模式
这里写图片描述


策略模式–算法的封装与切换

俗话说:条条大路通罗马。在很多情况下,实现某个目标的途径不止一条,例如我们在外出旅游时可以选择多种不同的出行方式,如骑自行车、坐汽车、坐火车或者坐飞机,可根据实际情况(目的地、旅游预算、旅游时间等)来选择一种最适合的出行方式。
在软件开发中,我们也常常会遇到类似的情况,实现某一个功能有多条途径,每一条途径对应一种算法,此时我们可以使用一种设计模式来实现灵活地选择解决途径,也能够方便地增加新的解决途径。

1.1定义

-策略模式 (Strategy Pattern):定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法可以独立于使用它的客户变化。
每一个封装算法的类称之为策略(Strategy)类
策略模式提供了一种可插入式(Pluggable)算法的实现方案

1.2情景实例

问题描述
-电影票打折方案
菜鸟软件公司为某电影院开发了一套影院售票系统,在该系统中需要为不同类型的用户提供不同的电影票打折方式,具体打折方案如下:
(1) 学生凭学生证可享受票价8折优惠;
(2) 年龄在10周岁及以下的儿童可享受每张票减免10元的优惠(原始票价需大于等于20元);
(3) 影院VIP用户除享受票价半价优惠外还可进行积分,积分累计到一定额度可换取电影院赠送的奖品。
该系统在将来可能还要根据需要引入新的打折方式。

初步思路分析
定义一个电影票类,在类中根据参数进行判断区分不同的种类,然后进行对应的打折措施

实例关键源代码

 //电影票类  
    class MovieTicket
    {
        private double price; //电影票价格  
        private String type; //电影票类型  

        public void setPrice(double price)
        {
            this.price = price;
        }

        public void setType(String type)
        {
            this.type = type;
        }

        public double getPrice()
        {
            return this.calculate();
        }

        //计算打折之后的票价  
        public double calculate()
        {
            //学生票折后票价计算  
            if (this.type.Equals("student"))
            {
                Console.WriteLine("学生票:");
                return this.price * 0.8;
            }
            //儿童票折后票价计算  
            else if (this.type.Equals("children") && this.price >= 20)
            {
                Console.WriteLine("儿童票:");
                return this.price - 10;
            }
            //VIP票折后票价计算  
            else if (this.type.Equals("vip"))
            {
                Console.WriteLine("VIP票:");
                Console.WriteLine("增加积分!");
                return this.price * 0.5;
            }
            else
            {
                return this.price; //如果不满足任何打折要求,则返回原始票价  
            }
        }
    }
     class Program
    {
        static void Main(string[] args)
        {
            MovieTicket mt = new MovieTicket();
            double originalPrice = 60.0; //原始票价  
            double currentPrice; //折后价  

            mt.setPrice(originalPrice);
            Console.WriteLine("原始价为:" + originalPrice);
            Console.WriteLine("---------------------------------");

            mt.setType("student"); //学生票  
            currentPrice = mt.getPrice();
            Console.WriteLine("折后价为:" + currentPrice);
            Console.WriteLine("---------------------------------");

            mt.setType("children"); //儿童票  
            currentPrice = mt.getPrice();
            Console.WriteLine("折后价为:" + currentPrice);
            Console.ReadLine();
        }
    }

现有缺点(未来变化)
(1) MovieTicket类的calculate()方法非常庞大,它包含各种打折算法的实现代码,在代码中出现了较长的if…else…语句,不利于测试和维护。
(2) 增加新的打折算法或者对原有打折算法进行修改时必须修改MovieTicket类的源代码,违反了“开闭原则”,系统的灵活性和可扩展性较差。
(3) 算法的复用性差,如果在另一个系统(如商场销售管理系统)中需要重用某些打折算法,只能通过对源代码进行复制粘贴来重用,无法单独重用其中的某个或某些算法(重用较为麻烦)。

如何改进
导致产生这些问题的主要原因在于MovieTicket类职责过重,它将各种打折算法都定义在一个类中,这既不便于算法的重用,也不便于算法的扩展。因此我们需要对MovieTicket类进行重构,将原本庞大的MovieTicket类的职责进行分解,将算法的定义和使用分离,这就是策略模式所要解决的问题

新的UML类图
这里写图片描述
改进关键源代码

     class MovieTicket
    {
        private double price;
        private Discount discount; //维持一个对抽象折扣类的引用

        //注入一个折扣类对象
        public void SetDiscount(Discount discount)
        {
            this.discount = discount;
        }

        public double Price
        {
            get {
                //调用折扣类的折扣价计算方法
                return discount.Calculate(this.price); 
            }
            set { price = value; }
        }
    }
    interface Discount
    {
        //打折策略接口
        double Calculate(double price);
    }
    class StudentDiscount : Discount
    {
        //具体打折策略 其他VIP和儿童打折省略
        private const double DISCOUNT = 0.8; 
        public double Calculate(double price) 
        {
            Console.WriteLine("学生票:");
            return price * DISCOUNT;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            MovieTicket mt = new MovieTicket();
            double originalPrice = 60.0;
            double currentPrice;

            mt.Price = originalPrice;
            Console.WriteLine("原始价为:{0}",originalPrice);
            Console.WriteLine("---------------------------------");

            Discount discount;
            //读取配置文件
            string discountType = ConfigurationManager.AppSettings["discountType"];
            //反射生成具体折扣对象
            discount = (Discount)Assembly.Load("StrategySample").CreateInstance(discountType);
            //注入折扣对象
            mt.SetDiscount(discount); 

            currentPrice = mt.Price;
            Console.WriteLine("折后价为:{0}",currentPrice);
            Console.Read();
        }
    }

1.3模式分析

动机和意图

  • 实现某个目标的途径不止一条,可根据实际情况选择一条合适的途径。并且有可能新增新的策略。

一般结构

  • 策略模式包含4个角色:
  • Context(环境类):使用算法的角色,类中维持一个对抽象策略类的引用
  • Strategy(抽象策略类):所有策略类的父类,包含公共的抽象策略算法方法。
  • ConcreteStrategy(具体策略类):实现了在抽象策略类中声明的算法。

    策略模式UML类图
    这里写图片描述

改进后的优点

(1) 策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法,也可以灵活地增加新的算法。
(2) 使用策略模式可以避免多重条件选择语句。比直接继承环境类的办法还要原始和落后。
(3) 策略模式提供了一种算法的复用机制,将算法单独抽象封装在策略类中,方便地复用这些策略类(关联方式)。

现存的缺点

(1) 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法。
(2) 策略模式将造成系统产生很多具体策略类,任何细小的变化都将导致系统要增加一个新的具体策略类。
(3) 无法同时在客户端使用多个策略类,在使用策略模式时,客户端每次只能使用一个策略类,不支持使用一个策略类完成部分功能后再使用另一个策略类来完成剩余功能的情况。

适用场景
(1) 一个系统需要动态地在几种算法中选择一种,那么这些算法可以包装到一个个具体的算法类里面,并为这些具体的算法类提供一个统一的接口。
(2) 一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重条件选择语句来实现。此时,使用策略模式,把这些行为转移到相应的具体策略类里面,就可以避免使用难以维护的多重条件选择语句。
(3) 不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法与相关的数据结构,可以提高算法的保密性与安全性。

举例:游戏VIP充值送礼品 王者荣耀段位火锅店打折系列

实例源代码
GitHub地址
百度云地址:链接: https://pan.baidu.com/s/1nvxdnrr 密码: dp7t

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值