策略模式

背景:需要一个鸭子类,包括绿头鸭,红头鸭,橡皮鸭等等,他们具有叫声,飞行,外貌长相等特性。

1. 先做一个不那么恰当的例子

 //1.先做一个Duck class
    public abstract class SimUDuck
    {
        void quack()
        {
            Console.WriteLine("鸭子都会嘎嘎叫");
        }
        void swim()
        {
            Console.WriteLine("鸭子都会游泳");
        }
      public  abstract void display();//每个鸭子类型看起来都不一样,所以做成抽象方法

        //这样写在父类里面,那么不能飞的鸭子也继承了飞的行为,如下写是不对的:
        //void fly()
        //{
        //    Console.WriteLine("新增的需求:让某些鸭子能飞");
        //}

        public abstract void fly();//让子类自己去判断能不能飞,但是这样代码量会变大,因为每一个继承它的子类都要复写一遍这个方法
    }

    class MallardDuck : SimUDuck
    { 
        public override void display() 
        {
            Console.WriteLine("看起来像绿头鸭");
        }
        public override void fly() 
        {
            Console.WriteLine("绿头鸭会飞");
        }
    }

    class RedHeadDuck : SimUDuck
    {
        public override void display()
        {
            Console.WriteLine("看起来像红头鸭");
        }

        public override void fly()
        {
            Console.WriteLine("红头鸭会飞");
        }
    }

    class RubberDuck : SimUDuck
    {
        public override void display()
        {
            Console.WriteLine("看起来像橡皮鸭");
        }

        public override void fly()
        {
            Console.WriteLine("橡皮鸭会飞的方法写成空,表示橡皮鸭不能飞");//代码量会变大,因为每一个继承它的子类都要复写一遍这个方法,这样也是不行的
        }
    }

在上面的例子里面:

  • 鸭子的叫声和游泳可能也是变化的,在父类中写定了是不行的
  • 如果在父类中写定一个飞的实例方法,子类继承后,那么本来不应该会飞的子类也有了会飞的特性了。
  • 如果父类方法全部做成抽象类,每一次子类的继承就要去重写一遍父类方法,就算有些鸭子不会飞,那也要重写为不会飞,这也是很低效的。

2. 用接口和算法簇实现

//1.先做一个Duck class
    public abstract class SimUDuck
    {
        //声明接口的引用关联关系
        public IFlyBehavior flyBehavior;
        public IQuackBehavior quackBehavior;
        public void performFly()//父类不具体实现飞的方法,委托给接口
        {
            flyBehavior.fly();
        }
       public void performQuack() //父类不具体实现叫的方法,委托给接口
        {
            quackBehavior.quack();
        }
        void swim()//所有父类和子类都一致的方法
        {
            Console.WriteLine("鸭子都会游泳");
        }
        public void setFlyBehavior(IFlyBehavior fb)//这两个方法用来动态实现具体的方法,而不是像原来一样写在构造函数里
        {
            flyBehavior = fb;
        }
        public void setQuackBehavior(IQuackBehavior qb)
        {
            quackBehavior = qb;
        }
        public abstract void display();//每个子类的都有的方法,但是每个子类的表现却不一样,所以做成抽象方法子类重写
    }

    class MallardDuck : SimUDuck
    {
        public MallardDuck()
        {
            base.quackBehavior = new Quack();//使用父类的字段
            base.flyBehavior = new FlyWithWings();
        }
        public override void display()
        {
            Console.WriteLine("看起来像绿头鸭");
        }
    }

    class RedHeadDuck : SimUDuck
    {
        public override void display()
        {
            Console.WriteLine("看起来像红头鸭");
        }
    }

    class RubberDuck : SimUDuck
    {
        public override void display()
        {
            Console.WriteLine("看起来像橡皮鸭");
        }
    }
    class DecoyDuck : SimUDuck
    {
        public override void display()
        {
            Console.WriteLine("看起来像诱饵鸭");
        }
    }
    class FlyWithWings:IFlyBehavior
    {
        public void fly()
        {
            Console.WriteLine("有翅膀可以飞");
        }
    }
    class FlyNoWay : IFlyBehavior
    {
        public void fly()
        {
            Console.WriteLine("没有翅膀不能飞");
        }
    }
    class Quack: IQuackBehavior
    {
        public void quack()
        {
            Console.WriteLine("嘎嘎叫");
        }
    }
    class Squeack: IQuackBehavior
    {
        public void quack()
        {
            Console.WriteLine("吱吱叫");
        }
    }
    class MuteQuack: IQuackBehavior
    {
        public void quack()
        {
            Console.WriteLine("不会叫");
        }
    }
    public interface IFlyBehavior//把变化的飞行行为做成接口
    {
        void fly();
    }
    public interface IQuackBehavior//把变化的叫声行为做成接口
    {
        void quack();
    }

public static void main(String[] args)
{
    SimUDuck mallard = new MallardDuck();
    mallard.performFly();
    mallard.performQuack();
}
  • 实现写在具体的类里面
  • 父类关联具体的接口引用,实现算法的具体类关联接口
  • 策略模式

数据流向如下:

//MallarDuck的实现(在主函数中)
    SimUDuck mallard = new MallardDuck();
    mallard.performFly();
    mallard.performQuack();
//1.去MallarDuck()的构造函数,构造函数里面把Mallarduck()实际用到的算法类关联给了父类的接口引用变量
public MallardDuck()
        {
            base.quackBehavior = new Quack();//使用父类的字段
            base.flyBehavior = new FlyWithWings();
        }
//2.子类继承的performFly()和performQuack()方法直接进入了父类的该方法,父类对该方法做了如下委托
       public void performFly()//父类不具体实现飞的方法,委托给接口
        {
            flyBehavior.fly();
        }
       public void performQuack() //父类不具体实现叫的方法,委托给接口
        {
            quackBehavior.quack();
        }
//3.因为先前在子类构造函数中已经关联了实际算法类,performFly()和performQuack()则会直接去到对应的算法类实例中:
    class Quack: IQuackBehavior
    {
        public void quack()
        {
            Console.WriteLine("嘎嘎叫");
        }
    }
    class FlyWithWings:IFlyBehavior
    {
        public void fly()
        {
            Console.WriteLine("有翅膀可以飞");
        }
    }
//4. 执行算法类中的算法实例
>>> 嘎嘎叫
>>> 有翅膀可以飞

3. 总结

  • 在设计模式的时候,原则上把会变化的部分提取并封装,这样以后就可以只修改或者拓展这个部分,而不需要引起其他不必要变化的部分改变。
  • 为同类行为提供一个固定的接口,针对接口编程,而不是针对具体的实例对象编程。
  • 如上面的例子,使用算法的组合好处优于不断的继承。
  • 学会看类图:- - - |>,表示接口(实现implements)————|>,表示继承(拓展)——————>,表示存在(has-a)
  • 在上面的例子中为什么倾向于使用SimUDuck mallard = new MallardDuck()而不是MallardDuck mallard = new MallardDuck(),理由如下:
1. 多态性的体现:
   使用父类的引用就可以利用多态性,在运行时可以把变量引用给任何父类的子类,使得代码的灵活性和拓展性更好,如下:
Duck myDuck;
myDuck = new MallardDuck();
myDuck = new RubberDuck();

2. 接口编程
   当使用父类或者接口类型作为变量类型时,可以轻易地实现接口编程,这意味着代码将更关注鸭子的行为(公共方法和属性),而不是具体的实现,有助于减少对实现的依赖,提高代码的可维护性,如下:
public MallardDuck()
        {
            base.quackBehavior = new Quack();//使用父类的字段
            base.flyBehavior = new FlyWithWings();
        }

3. 依赖倒置原则
   高层模块不应该依赖于低层模块,而应该依赖于抽象。这种情况下,Duck是一个抽象层次,而MallardDuck是一个具体的实现层次。通过依赖Duck而不是MallardDuck,可以更轻松的更改或者拓展实现而不改变其他使用Duck的对象的代码。

4. 在设计模式上来说,依赖这一类多态和抽象类型,可以更灵活。
posted @ 2024-09-23 23:18  不愿透露姓名的小村村  阅读(17)  评论(0编辑  收藏  举报