前言:
继续拜读经典,学习简单工厂模式以及策略模式
简单工厂模式
计算器
我们先来以面向对象的思想来做一个简单的计算器:
Operation.cs
class Operation
{
private double _numberA = 0;
private double _numberB = 0;
public double NumberA {
get { return _numberA; }
set { _numberA = value; }
}
public double NumberB
{
get { return _numberB; }
set { _numberB = value; }
}
public virtual double GetResult() {
double result = 0;
return result;
}
}
OperationAdd.cs
class OperationAdd:Operation
{
public override double GetResult()
{
double result = 0;
result = NumberA + NumberB;
return result;
}
}
class OperationSub : Operation
{
public override double GetResult()
{
double result = 0;
result = NumberA - NumberB;
return result;
}
}
class OperationMul : Operation
{
public override double GetResult()
{
double result = 0;
result = NumberA * NumberB;
return result;
}
}
class OperationDiv : Operation
{
public override double GetResult()
{
double result = 0;
if (NumberB == 0) {
throw new Exception("除数不能为0");
}
result = NumberA / NumberB;
return result;
}
}
上面就是计算器的核心代码,面向对象让我们很好的封装了这些代码,极大地降低了耦合度。
如果需要再加入别的运算也是非常简单的。
简单工厂
上面计算器的运算类是有了,但是实例化又是一个问题,怎么样可以有规格的实例化对象呢?
我们引入正题
class OperationFactory
{
public static Operation createOperate(string operate) {
Operation oper = null;
switch (operate) {
case "+":
oper = new OperationAdd();
break;
case "-":
oper = new OperationSub();
break;
case "*":
oper = new OperationMul();
break;
case "/":
oper = new OperationDiv();
break;
}
return oper;
}
}
所以在主类中,可以直接:
class Program
{
static void Main(string[] args)
{
Operation oper;
oper = OperationFactory.createOperate("+");
oper.NumberA = 1;
oper.NumberB = 2;
Console.WriteLine(oper.GetResult());
}
}
这就是简单工厂模式,我们将类的实例化单独处理在一个类中。
UML类图:
要认识UML类图,要从UML是什么开始.UML(Unified Modeling Language)即统一建模语言,是用于系统可视化建模语言。它是国际统一软件建模标准,融合了OMT、OOSE、Booch方法中的建模语言。
UML是一种可视化、可用于详细描述、文档化的语言。UML就像数学中的数字和加减符号一样,为所有软件开发的人员提供了一种图形化表达、标准化的语言。通过UML,软件开发人员可以准备的描述软件结构和建模,并通过UML建立整个系统架构和详细文档。
UML类图正是UML建模元素中的一种。
我们来看一个示例:
- 图中矩形实线方框代表一个类,这个类有类名(斜体则为抽象类)、特性、操作。
- 特性、操作前面的符号代表了修饰符:
- +:public
- -:private
- #:protected
- <<interface>>和“棒棒糖”都表示这是一个接口
- 图中的关系:
- 聚合关系:一种弱的拥有关系,例如:雁群类 中有大雁类数组对象
- 依赖关系:一种依赖,例如动物类 中的方法需要“氧气”、“水”类型的形参
- 组合(合成)关系:一种强的拥有关系,体现了严格的部分和整体。例如鸟类 中会定义一个实例化的 翅膀类
- 关联关系:一个类需要对另一个类有了解。例如在 企鹅类 中引用了 气候类
这里我将优快云上一个文章做了个大截图,体现类与类之间的关系:
策略模式
银行收银系统
我们先来通过UI界面简单实现一个“银行收银系统”
单价、数量、总计、0.00都是标签(label)
两个TextBox
一个ListBox,并且将SelectionMode关闭
确定、重置是button
另外给组件们适当设置锚点
我们来给“确定”添加事件代码:
double total = 0.0d;
private void submit_Click(object sender, EventArgs e)
{
//计算单价*数量的总价
double totalPrices = Convert.ToDouble(txtPrice.Text)
* Convert.ToDouble(txtNum.Text);
total = total + totalPrices;
lbxList.Items.Add("单价:" + txtPrice.Text + " 数量: " +
txtNum.Text + " 合计:" + totalPrices.ToString());
lblResult.Text = total.ToString();
}
txtPrice、txtNum、lbxList、lblResult 都是组件输入框的name属性
系统优化
考虑到打折的情况,我们可以再增加一个打折功能
添加一个ComboBox,在程序总面板加载的时候对ComboBox进行初始化:
double total = 0.0d;
private void Form1_Load(object sender, EventArgs e)
{
cbxType.Items.AddRange(new object[] { "正常收费", "打八折", "打七折", "打五折" });
cbxType.SelectedIndex = 0;
}
private void submit_Click(object sender, EventArgs e)
{
//计算单价*数量的总价
double totalPrices = 0d;
switch (cbxType.SelectedIndex) {
case 0:
totalPrices = Convert.ToDouble(txtPrice.Text) *
Convert.ToDouble(txtNum.Text);
break;
case 1:
totalPrices = Convert.ToDouble(txtPrice.Text) *
Convert.ToDouble(txtNum.Text)*0.8f;
break;
case 2:
totalPrices = Convert.ToDouble(txtPrice.Text) *
Convert.ToDouble(txtNum.Text)*0.7f;
break;
case 3:
totalPrices = Convert.ToDouble(txtPrice.Text) *
Convert.ToDouble(txtNum.Text)*0.5f;
break;
}
total = total + totalPrices;
lbxList.Items.Add("单价:" + txtPrice.Text + " 数量: " +
txtNum.Text + " 合计:" + totalPrices.ToString());
lblResult.Text = total.ToString();
}
然后我们就实现了对应打折的需求。
打折需求对应之后,不禁又犯了难。
如果商店要乱搞,例如满300送200的活动,那我们的项目又应该怎么做?
而且上面的代码中,光是Convert.ToDouble就写了八次,不免有些“代码复用”。
该怎么办呢?我们要弄很多类吗?
面向对象的编程,并不是类越多越好,类的划分是为了封装,但分类的基础是抽象,具有相同属性和功能的对象的抽象集合才能是类。
简单工厂实现
接下来我们来结合前面学过的简单工厂模式来实现
CashSuper.cs
abstract class CashSuper
{
public abstract double acceptCash(double money);
}
Cash.cs
class CashNormal : CashSuper
{
public override double acceptCash(double money)
{
return money;
}
}
class CashRebate : CashSuper
{
private double moneyRebate = 1d;
public CashRebate(string moneyRebate) {
this.moneyRebate = double.Parse(moneyRebate);
}
public override double acceptCash(double money)
{
return moneyRebate * money;
}
}
class CashReturn : CashSuper
{
private double moneyCondition = 0.0d;
private double moneyReturn = 0.0d;
public CashReturn(string moneyCondition, string moneyReturn) {
this.moneyCondition = double.Parse(moneyCondition);
this.moneyReturn = double.Parse(moneyReturn);
}
public override double acceptCash(double money)
{
double result = money;
if (money >= moneyCondition)
result = money - Math.Floor(money / moneyCondition) * moneyReturn;
return result;
}
}
CashFactory.cs
class CashFactory
{
//现金收取工厂
public static CashSuper createCashAccept(string type) {
CashSuper cs = null;
switch (type) {
case "正常收费":
cs = new CashNormal();
break;
case "满300返100":
CashReturn cr1 = new CashReturn("300", "100");
cs = cr1;
break;
case "打8折":
CashRebate cr2 = new CashRebate("0.8");
cs = cr2;
break;
}
return cs;
}
}
客户端:
double total = 0.0d;
private void Form1_Load(object sender, EventArgs e)
{
cbxType.Items.AddRange(new object[] { "正常收费", "满300返100", "打8折" });
cbxType.SelectedIndex = 0;
}
private void submit_Click(object sender, EventArgs e)
{
CashSuper csuper = CashFactory.createCashAccept(cbxType.SelectedItem.ToString());
double totalPrices = 0;
totalPrices = csuper.acceptCash(Convert.ToDouble(txtPrice.Text)*
Convert.ToDouble(txtNum.Text));
total = total + totalPrices;
lbxList.Items.Add("单价:" + txtPrice.Text + " 数量: " +
txtNum.Text + " 合计:" + totalPrices.ToString());
lblResult.Text = total.ToString();
}
利用简单工厂模式,我们实现了很方便的生成对象控制,这样不管想要添加什么花里胡哨的商业活动,都可以很容易的实现。
但是这个模式只是解决对象的创建问题,而且由于工厂本身包括了所有的收费方式,每次维护或扩展收费方式都要重新编译部署,这是很糟糕的处理方式,所以简单工厂模式并不是最优的处理模式。
接下来,我们来见识一下“策略模式”
策略模式
策略模式定义了算法家族,分别封装起来,让他们之间互相转换,此模式让算法变化,不会影响到使用算法的客户。
下面是策略模式的模板
Strategy.cs
abstract class Strategy
{
//算法方法
public abstract void AlgorithmInterface();
}
Concrete.cs
class ConcreteStrategyA : Strategy
{
//算法A实现方法
public override void AlgorithmInterface()
{
Console.WriteLine("算法A实现");
}
}
class ConcreteStrategyB : Strategy
{
//算法B实现方法
public override void AlgorithmInterface()
{
Console.WriteLine("算法B实现");
}
}
class ConcreteStrategyC : Strategy
{
//算法C实现方法
public override void AlgorithmInterface()
{
Console.WriteLine("算法C实现");
}
}
Context.cs(维护对一个Strategy对象的引用)
class Context
{
//与Strategy是一个组合关系(鸟与翅膀)
Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
//上下文接口
public void ContextInterface() {
strategy.AlgorithmInterface();
}
}
Program.cs
class Program
{
static void Main(string[] args)
{
Context context;
context = new Context(new ConcreteStrategyA());
context.ContextInterface();
context = new Context(new ConcreteStrategyB());
context.ContextInterface();
context = new Context(new ConcreteStrategyC());
context.ContextInterface();
Console.Read();
}
}
策略模式实现
回到我们的收银系统,CashSuper 就是 Strategy :算法基类;CashNormal、CashRebate、CashReturn 就是 继承 CashSuper 的具体算法类,那么我们只需要添加一个CashContext类,并修改一下客户端即可:
CashContext类:
class CashContext
{
private CashSuper cs;
public CashContext(CashSuper csuper) {
//传入具体收费策略
this.cs = csuper;
}
public double GetResult(double money) {
return cs.acceptCash(money);
}
}
客户端 from1.cs
double total = 0.0d;
private void Form1_Load(object sender, EventArgs e)
{
cbxType.Items.AddRange(new object[] { "正常收费", "满300返100", "打8折" });
cbxType.SelectedIndex = 0;
}
private void submit_Click(object sender, EventArgs e)
{
CashContext cc = null;
switch (cbxType.SelectedItem.ToString()) {
case "正常收费":
cc = new CashContext(new CashNormal());
break;
case "满300返100":
cc = new CashContext(new CashReturn("300","100"));
break;
case "打8折":
cc = new CashContext(new CashRebate("0.8"));
break;
}
/*通过对cashContext的getResult的调用
可以得到最后收费结果,让算法与客户端实现了隔离*/
double totalPrices = 0;
totalPrices = cc.GetResult(Convert.ToDouble(txtPrice.Text)*
Convert.ToDouble(txtNum.Text));
total = total + totalPrices;
lbxList.Items.Add("单价:" + txtPrice.Text + " 数量: " +
txtNum.Text + " 合计:" + totalPrices.ToString());
lblResult.Text = total.ToString();
}
我们这样就利用了策略模式,将具体算法与客户端实现了分离。
不过,聪明的朋友可能也发现了,我们又让客户端来处理繁琐的switch判断功能了,这显然是不合适的。
于是我们可以结合目前的两种设计模式。
策略模式结合简单工厂模式
CashContext.cs
class CashContext
{
private CashSuper cs;
public CashContext(string type) {
//将实例化具体策略的过程由客户端转移到Context类中,简单工厂的应用
switch (type)
{
case "正常收费":
cs = new CashNormal();
break;
case "满300返100":
cs = new CashReturn("300", "100");
break;
case "打8折":
cs = new CashRebate("0.8");
break;
}
}
public double GetResult(double money) {
return cs.acceptCash(money);
}
}
form1.cs
double total = 0.0d;
private void Form1_Load(object sender, EventArgs e)
{
cbxType.Items.AddRange(new object[] { "正常收费", "满300返100", "打8折" });
cbxType.SelectedIndex = 0;
}
private void submit_Click(object sender, EventArgs e)
{
CashContext csuper = new CashContext(cbxType.SelectedItem.ToString());
double totalPrices = 0;
totalPrices = csuper.GetResult(Convert.ToDouble(txtPrice.Text)*
Convert.ToDouble(txtNum.Text));
total = total + totalPrices;
lbxList.Items.Add("单价:" + txtPrice.Text + " 数量: " +
txtNum.Text + " 合计:" + totalPrices.ToString());
lblResult.Text = total.ToString();
}
简单工厂模式不一定非要建一个工厂类,直接这样用也行。
这样,我们就实现了客户端传来一个“类型”,我们反馈一个算法,耦合性非常低。
策略模式解析
策略模式是一种定义一系列算法的方法,从概念上看,所有这类算法完成的都是相同的工作,只是实现不同,它可以用相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。
策略模式的Strategy类层次为Context定义了一系列可供重用的算法或行为。继承有助于析取出这些算法中的共同功能。
另外策略模式的优点是简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试(Java表示:“老子有JUnit,不需要(开个玩笑)”)。
策略模式主要就是用来封装算法。在实践中,只要在分析过程中听到需要在不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性。
里面还是有繁琐的switch语句,有需求变更还是免不了一些大改动,其实如上还可以再优化,后面的“反射技术”会再打开你的眼界!(反射反射,程序员的快乐!)
商业转载 请联系作者获得授权,非商业转载 请标明出处,谢谢