zoukankan      html  css  js  c++  java
  • Head First设计模式之策略模式(Strategy Pattern)

    前言:

    刚刚开始学习设计模式,之前也接触过一些,但是从来都没有系统的学过,这次打算好好的学习一下。这里就当是对学习过程的一个记录、整理,以便可以在以后不时的温故知新。

    这一节采用一个鸭子的示例,层层推进,引入策略模式。具体如下:

    1.   基本需求:创建有一些特性的鸭子

    鸭子拥有如下的一些特性:游泳戏水、呱呱叫、外观

    初步实现鸭子的特性:

    鸭子超类:

    public abstract class Duck
        {
            public void Quack()
            {
                Console.WriteLine("鸭子叫:呱呱呱");
            }
            public void Swim()
            {
                Console.WriteLine("鸭子游泳");
            }
            public abstract voidDisplay();
        }

    鸭子子类:

    public class MallardDuck:Duck
        {
            public override voidDisplay()
            {
                Console.WriteLine("绿头鸭");
            }
        }
     
        public class RedHeadDuck : Duck
        {
            public override voidDisplay()
            {
                Console.WriteLine("红头鸭");
            }
        }

    2.   新的需求:让鸭子飞

    2.1 在Duck类中添加飞的方法

    如下:

     public abstract class Duck
        {
            public void Quack()
            {
                Console.WriteLine("鸭子叫:呱呱呱");
            }
            public void Swim()
            {
                Console.WriteLine("鸭子游泳");
            }
     
            public void Fly()
            {
                Console.WriteLine("鸭子飞了");
            }
            public abstract voidDisplay();
        }          

    2.2 并非所有的鸭子都能飞

    这个时候会带来一下新的问题:

    并非所有的鸭子都能飞,比如:橡皮鸭

    并非所有的鸭子都能呱呱叫:比如:橡皮鸭

    对应的解决办法:

    2.3 覆盖鸭子超类中的Fly和Quack方法

    通过在橡皮鸭的实现中覆盖鸭子超类中的Fly和Quack方法

    如下:

    public class RubberDuck : Duck
        {
            public new void Fly()
            {
                //Do nothing
            }
     
            public new void Quack()
            {
                Console.WriteLine("鸭子叫:吱吱吱");
            }
    }

    2.4 有些鸭子既不能飞也不能叫

    这样带来的新的问题:

    比如木头鸭,既不会游泳 也不会叫

    如果直接继承自Duck超类,还需要对Fly和Quack方法进行重写。

    这个时候我们可能意识到继承不是解决问题的最终办法,因为每一个继承自鸭子的子类都需要去被迫的检查并且可能要覆盖Fly和Quack方法。

    我们可能只需要让某些鸭子具有Fly和Quack即可

    2.5 通过接口让某些鸭子具有Fly和Quack特性

    通过接口来解决此问题

    如下所示:

    public interface IFlyable
        {
            public void Fly();
        }
     
        public interface IQuackable
        {
            public void Quack();
    }
     
    public class MoodDuck : IFlyable, IQuackable
        {
            public void Fly()
            {
                //Can't Fly
            }
     
            public void Quack()
            {
                //Can't Quack
            }
        }

    通过接口的方式虽然结局了一部分鸭子不会飞或者不会叫的问题,

    2.6 针对实现编程,代码不能复用

    使用接口产生新的问题:

    代码不能复用,产生过多的重复代码。如果你想要修改某个行为,必须在买个定义了此行为的类中修改它,这样很可能造成新的错误。

    2.7 封装变化,针对接口编程

    这个时候出现了第一个设计原则:

    找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码放在一起。

    即:

    把会变化的部分抽取并封装起来,以便以后可以轻易的改动或者扩充此部分,而不影响其他不变的部分。

    分开变化和不会变化的部分,在本例中Fly和Quack会随着鸭子的不同而改变,所以我们要将其抽离出Dock类,并建立一组新类来代表每个行为。

    这时出现了设计的另一个原则:

    针对接口编程,而不是针对实现编程。

    这里我们用接口代表每个行为,例如:IFlyable和IQuackable,每个行为的实现都将实现对应的接口。

    所以鸭子类不会负责实现IFlyable和IQuackable接口,而是由一组其他专门的类来实现对应的接口,称之为“实现类”。

    这种做法跟之前做法的区别:

    l  之前的做法中行为的实现来自超类的具体实现,或者继承某个接口的子类实现,这些做法都依赖于实现。

    l  新的设计中,鸭子子类通过使用接口表示行为,所以实现不会被绑定在子类中,特定的行为编写在实现了接口的类中。

    “针对接口编程”真正的意思是“针对超类型”编程

    变量的声明类型应该是超类型,通常是一个抽象类或者接口,因此只要是实现了抽象类或者接口的对象,都可以指定给这个变量,即声明类型时不用理会以后执行时真正的对象类型。

    接下来我们就可以实现鸭子具体的行为了,如下:

    /// 会飞的行为类
        /// </summary>
        public classFlyWithWings:IFlyable
        {
            public void Fly ()
            {
                Console.WriteLine("用翅膀飞");
            }
        }
     
        /// <summary>
        /// 不会飞的行为类
        /// </summary>
        public class FlyNoWay : IFlyable
        {
            public void Fly()
            {
                Console.WriteLine("不会飞");
            }
        }
     
        /// <summary>
        /// 呱呱叫的行为类
        /// </summary>
        public class Quack : IQuackable
        {
            public void Quack()
            {
                Console.WriteLine("呱呱叫");
            }
        }
     
        /// <summary>
        /// 吱吱叫的行为类
        /// </summary>
        public class Squeak : IQuackable
        {
            public void Quack()
            {
                Console.WriteLine("吱吱叫");
            }
        }
     
        /// <summary>
        /// 不会叫的行为类
        /// </summary>
        public class MuteQuack :IQuackable
        {
            public void Quack()
            {
                Console.WriteLine("不会叫");
            }
        }

    接下来我们要对鸭子的行为进行整合,其核心思想就是:将鸭子飞行和叫的行为委托别人处理,不使用定义在Duck类内的叫和飞行方法。

    具体做法如下:

    1.    在Duck类中新增2个实例变量,分别为“flyBehavior”和“quackBehavior”声明为接口类型(每个鸭子对象会动态的设置这些变量,在运行时引用正确的行为类型)

    2.    将Duck类中的Quack()和Fly()方法删除,同时新增PerformFly()和PerformQuack()来取代这两个类

    3.    实现PerformFly()和PerformQuack(),如下所示:

    /// Description:鸭子超类
        /// </summary>
        public abstract class Duck
        {
            public IFlyable flyBehavior;
            public IQuackablequackBehavior;
     
            public void PerformFly()
            {
                flyBehavior.Fly();
            }
     
            public void PerformQuack()
            {
                quackBehavior.Quack();
            }
     
            public void Swim()
            {
                Console.WriteLine("鸭子游泳");
            }
     
           
            public abstract voidDisplay();
        }

    这样做的结果就是我们可以忽略flyBehavior和quackBehavior接口对象到底是什么,只需要关心该对象如何进行相应的行为即可。

    4.    设定flyBehavior类和quackBehavior类的实例变量,如下所示:

     public class MallardDuck : Duck
        {
            public MallardDuck()
            {
                flyBehavior = newFlyWithWings();//使用FlyWithWings类处理飞行,当PerformFly()被调用时,飞的职责被委托给FlyWithWings对象,得到真正的飞
                quackBehavior = newQuack();
            }
     
            public override voidDisplay()
            {
                Console.WriteLine("绿头鸭");
            }
        }

    说明:

    当MallardDuck实例化的时候,构造器会把继承自quackBehavior的实例变量初始化成Quack类型的新实例,同样对于飞的行为也是如此。

    5.    测试,如下:

      Duck.Duck mallard = new MallardDuck();
    
                mallard.PerformFly();
    
                mallard.PerformQuack();
    
                Console.Read();

    结果如下:

    动态的设定行为

    为了能够充分的用到我们之前建的一些鸭子的动态行为,我们可以在鸭子子类中通过设定方法来设定鸭子的行为,而不是在样子的构造器内实例化。具体步骤如下:

    1.    在Duck类中新增两个设置方法,如下:

    public void SetFlyBehavior(IFlyable fly)
            {
                flyBehavior = fly;
            }
     
            public voidSetQuackBehavior(IQuackable quack)
            {
                quackBehavior = quack;
            }

    2.    创建新的鸭子类型

     public class ModelDuck : Duck
        {
            public ModelDuck()
            {
                flyBehavior = newFlyNoWay();
                quackBehavior = newSqueak();
            }
            public override void Display()
            {
                Console.WriteLine("模型鸭");
            }
        }

    3.    创建一个新的FlyBehavior类型

    public class FlyRocket : IFlyable
        {
            public void Fly()
            {
               Console.WriteLine("Fly with Rocket!");
            }
        }

    4.    测试

    Duck.Duck model = new ModelDuck();
                model.PerformFly();
                model.PerformQuack();
                model.SetFlyBehavior(newFlyRocket());
                model.PerformFly();

    结果如下:

    从上边的结果中我们可以看出,在初始化ModelDuck的时候,我们对flyBehavior对象进行了FlyNoWay的初始化,所以显示的飞行的行为为:不会飞,后边我们又通过SetFlyBehavior方法动态设置了flyBehavior的实例,所以后边就有了Fly with Rocket的行为啦。

    上边的例子对于每一个鸭子都有一个FlyBehavior和QuackBehavior,当你将两个类结合起来使用,就是组合,这种做法和继承的不同之处在于,鸭子的行为不是继承来的,而是合适的对象组合来的。这里用到了面向对象的第三个设计原则:

    多用组合,少用继承

    通过刚刚讲的这么多,引出了本章的第一个模式:

    策略模式:定义了算法簇(这里的算法簇就相当于一组行为),分别封装起来,让他们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。

    3.   总结

    通过本章的学习,我们可以了解掌握以下知识:

    1.    面向对象设计的原则(部分):

    l 封装变化

    l 多用组合,少用继承

    l 针对接口编程,不针对实现编程

    2.    面向对象模式---策略模式:

    定义算法簇,分别封装起来,让它们之间可以相互替换,此模式让算法变化独立于使用算法的客户。

  • 相关阅读:
    流程控制和数组
    数据类型和运算符
    JavaWeb(一)-Servlet中的Config和Context
    成语接龙
    java 解决树形结构数据 (有序无序通杀)
    java https
    git 使用教程
    Spring boot 解决跨域问题
    redis详解(四)--高可用分布式集群
    redis详解(三)--面试题
  • 原文地址:https://www.cnblogs.com/Olive116/p/5265007.html
Copyright © 2011-2022 走看看