现代交通的发达程度日益提高,人们出行的方式也越来越丰富。例如从杭州出发到上海,如果你很赶时间,同时又不差钱,那么你可以选择乘飞机出行;如果你不赶时间,但又不想浪费太多金钱,那么你可以选择乘坐大巴出行;如果你想在出行的同时能够顺便锻炼一下身体,那么骑车出行也是一个不错的选择。总而言之,在面对一个具体问题的时候,我们往往会根据实际情况做出不同的决策。下面看一张图:
某影院目前共有四种收费方式,学生票打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