拜读经典——大话设计模式(二)——简单工厂模式、策略模式

前言:

继续拜读经典,学习简单工厂模式以及策略模式

 

 

 

 

 


简单工厂模式

计算器

我们先来以面向对象的思想来做一个简单的计算器:

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>>和“棒棒糖”都表示这是一个接口
  • 图中的关系:
    • 聚合关系:一种弱的拥有关系,例如:雁群类 中有大雁类数组对象
    • 依赖关系:一种依赖,例如动物类 中的方法需要“氧气”、“水”类型的形参
    • 组合(合成)关系:一种强的拥有关系,体现了严格的部分和整体。例如鸟类 中会定义一个实例化的 翅膀类
    • 关联关系:一个类需要对另一个类有了解。例如在 企鹅类 中引用了 气候类

 

这里我将CSDN上一个文章做了个大截图,体现类与类之间的关系:

 

 

 

 

 

 


策略模式

银行收银系统

我们先来通过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语句,有需求变更还是免不了一些大改动,其实如上还可以再优化,后面的“反射技术”会再打开你的眼界!(反射反射,程序员的快乐!)

 

 


 

 

 

 

 

 

 

商业转载 请联系作者获得授权,非商业转载 请标明出处,谢谢

 

发表评论