C#策略模式的使用

现代交通的发达程度日益提高,人们出行的方式也越来越丰富。例如从杭州出发到上海,如果你很赶时间,同时又不差钱,那么你可以选择乘飞机出行;如果你不赶时间,但又不想浪费太多金钱,那么你可以选择乘坐大巴出行;如果你想在出行的同时能够顺便锻炼一下身体,那么骑车出行也是一个不错的选择。总而言之,在面对一个具体问题的时候,我们往往会根据实际情况做出不同的决策。下面看一张图:
在这里插入图片描述
某影院目前共有四种收费方式,学生票打8折,VIP打5折,儿童票可减免10元,成人票不打折。如果让你设计一个如下图所示的电影票金额结算程序,你会怎么设计?
在这里插入图片描述
有同志提出如下方法:

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
            this.cmbType.Items.Add("学生票");
            this.cmbType.Items.Add("会员票");
            this.cmbType.Items.Add("儿童票");
            this.cmbType.Items.Add("成人票");
            this.cmbType.SelectedIndex = 0;
        }

        // 计算票价
        private void btnCalculatePrice_Click(object sender, EventArgs e)
        {
            if (string.IsNullOrWhiteSpace(txtPrice.Text))
            {
                MessageBox.Show("请输入电影票原价", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            double price = 0.0;
            if (!double.TryParse(txtPrice.Text, out price))
            {
                MessageBox.Show("请输入正确的价格", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            double result = 0.0;
            if (cmbType.SelectedItem.ToString() == "学生票")
            {
                result = price * 0.8;
            }
            else if (cmbType.SelectedItem.ToString() == "会员票")
            {
                result = price * 0.5;
            }
            else if (cmbType.SelectedItem.ToString() == "儿童票")
            {
                if (price > 10)
                {
                    result = price - 10;
                }
                else
                {
                    result = price;
                }
            }
            else
            {
                result = price;
            }

            MessageBox.Show("最终价格为" + result + "元", "提示", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
        }
    }
}

从结果来看,确实已经实现了功能,但这种方法有个很明显的缺点,那就是当该影院推出了一种新的折扣计算方式的时候,例如老年票打4折,这时候我们不得不重新修改if…else代码,这很明显违反了开闭原则,因此这种情况下我们可以考虑使用策略模式解决问题。首先,我们可以对打折这一决策进行抽象,即定义一个IDiscountStrategy接口:

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

namespace WindowsFormsApplication1.StrategyPattern
{
    /// <summary>
    /// 打折策略
    /// </summary>
    public interface IDiscountStrategy
    {
        double Calculate(double price);
    }
}

因为学生票打8折,所以可以定义一个学生票折扣类继承该接口:

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

namespace WindowsFormsApplication1.StrategyPattern
{
    /// <summary>
    /// 学生票打八折
    /// </summary>
    public class StudentDiscount : IDiscountStrategy
    {
        public double Calculate(double price)
        {
            return price * 0.8;
        }
    }
}

以此类推,其余的三种打折方式如下代码所示:

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

namespace WindowsFormsApplication1.StrategyPattern
{
    /// <summary>
    /// VIP打五折
    /// </summary>
    public class VipDiscount : IDiscountStrategy
    {
        public double Calculate(double price)
        {
            return price * 0.5;
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WindowsFormsApplication1.StrategyPattern
{
    /// <summary>
    /// 儿童票减十元(价格大于10元时)
    /// </summary>
    public class ChildDiscount : IDiscountStrategy
    {
        public double Calculate(double price)
        {
            return price > 10 ? price - 10 : price;
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WindowsFormsApplication1.StrategyPattern
{
    /// <summary>
    /// 成人票不打折
    /// </summary>
    public class AdultDiscount : IDiscountStrategy
    {
        public double Calculate(double price)
        {
            return price;
        }
    }
}

接着定义一个电影票类,CalculatePrice(IDiscountStrategy strategy)其实就是一个依赖注入点,该方法会根据折扣类型的不同自动计算打折后的金额。

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

namespace WindowsFormsApplication1.StrategyPattern
{
    /// <summary>
    /// 电影票
    /// </summary>
    public class MovieTicket
    {
        /// <summary>
        /// 电影票价格
        /// </summary>
        private double price;
        public double Price
        {
            get { return price; }
            set { price = value; }
        }

        /// <summary>
        /// 计算票价
        /// </summary>
        /// <param name="strategy"></param>
        /// <returns></returns>
        public double CalculatePrice(IDiscountStrategy strategy)
        {
            return strategy.Calculate(price);
        }
    }
}

在这里我们可以发现一个问题,利用依赖注入虽然可以使得CalculatePrice方法不依赖具体的对象类型,但可惜的是依赖注入本身并不能够解决对象的创建问题,因此这里我们考虑利用反射创建对象。打开配置文件进行如下配置:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
    </startup>
    <appSettings>
        <add key="学生票" value="StudentDiscount" />
        <add key="会员票" value="VipDiscount" />
        <add key="儿童票" value="ChildDiscount" />
        <add key="成人票" value="AdultDiscount" />
    </appSettings>
</configuration>

然后读取配置文件项,将其添加到下拉框中:

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Configuration;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using WindowsFormsApplication1.StrategyPattern;

namespace WindowsFormsApplication1
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
            foreach (string key in ConfigurationManager.AppSettings.AllKeys)
            {
                this.cmbType.Items.Add(key);
            }
            this.cmbType.SelectedIndex = 0;
        }
    }
}

这样做的好处很明显,那就是当需要新增一个或删除一个打折类型的时候,我们无需修改现有代码,只需要在配置文件中新增或删除对应项就OK了。最后就是计算票价了,当我们选定了一种折扣类型时,可以通过该类型的名称获取配置文件中对应的类名,然后利用获取到的类名结合反射创建对象。

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Configuration;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using WindowsFormsApplication1.StrategyPattern;

namespace WindowsFormsApplication1
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
            foreach (string key in ConfigurationManager.AppSettings.AllKeys)
            {
                this.cmbType.Items.Add(key);
            }
            this.cmbType.SelectedIndex = 0;
        }

        // 计算票价
        private void btnCalculatePrice_Click(object sender, EventArgs e)
        {
            if (string.IsNullOrWhiteSpace(txtPrice.Text))
            {
                MessageBox.Show("请输入电影票原价", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            double price = 0.0;
            if (!double.TryParse(txtPrice.Text, out price))
            {
                MessageBox.Show("请输入正确的价格", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            // 反射创建对象
            string assemblyName = "WindowsFormsApplication1";
            string className = ConfigurationManager.AppSettings[this.cmbType.SelectedItem.ToString()];
            IDiscountStrategy discount = Assembly.Load(assemblyName).CreateInstance(assemblyName + ".StrategyPattern." + className) as IDiscountStrategy;

            // 计算票价
            MovieTicket ticket = new MovieTicket()
            {
                Price = price
            };
            MessageBox.Show("最终价格为" + ticket.CalculatePrice(discount).ToString() + "元", "提示", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
        }
    }
}

可以发现,该方法完全符合开闭原则,当新增一种折扣类型时,只需要定义一个类继承IDiscountStrategy接口,然后在配置文件中添加该折扣类型即可。程序运行结果如下图所示:
在这里插入图片描述
源代码:https://pan.baidu.com/s/1Y8b9Qrs5AU8BTsPz7T8z2w
提取码:r1g0

发布了99 篇原创文章 · 获赞 16 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/HerryDong/article/details/102942225