2023-03-27
原文作者:ClarenceZero 原文地址:https://blog.csdn.net/ClarenceZero/article/details/106887075

策略模式

什么是策略

一般是指:

  1. 可以实现目标的方案集合。
  2. 根据形势发展而制定的行动方针和斗争方法。
  3. 有斗争艺术,能注意方式方法。

“策略”就是为了实现某一个目标,首先预先根据可能出现的问题制定的若干对应的方案,并且,在实现目标的过程中,根据形势的发展和变化来制定出新的方案,或者根据形势的发展和变化来选择相应的方案,最终实现目标。

(引用百度百科)

什么是策略模式

策略模式作为一种软件设计模式,指对象有某个行为,但是在不同的场景中,该行为有不同的实现算法。比如每个人都要“交个人所得税”,但是“在美国交个人所得税”和“在中国交个人所得税”就有不同的算税方法。

策略模式:

  • 定义了一族算法(业务规则);
  • 封装了每个算法;
  • 这族的算法可互换代替(interchangeable)。

策略模式UML图

(引用维基百科)

类图

202303272245060731.png

  • Client

    发起算法调用

  • Context(客户端)

    持有具体策略实现的引用,调用策略接口API,进行方法调用。

    可以持有全部策略引用,根据不同的策略标识获取已初始化的策略类,进而执行对应的策略行为。也可以通过构造方法传入策略类。

  • Strategy(策略接口)

    决定实现策略所必需的接口。

  • AddStrategy(具体策略实现者)

    实现策略接口,具体策略有不同的实现方式。达到解耦的目的。

实现

注。以下实现参考 <Head First 设计模式>

202303272245070302.png

1. 定义策略接口

    /**
     * 剥离出来的特定的飞行行为
     */
    public interface FlyBehavior {
        void fly();
    }
    
    
    /**
     * 剥离出来的特定的呼叫行为
     */
    public interface QuackBehavior {
        void quack();
    }

2. 定义策略实现类

    public class Quack implements QuackBehavior{
        @Override
        public void quack() {
            System.out.println("定义会呱呱叫的鸭子行为!!!");
        }
    }
    
    public class FlyWithWings implements FlyBehavior{
        @Override
        public void fly() {
            System.out.println("定义会飞的鸭子行为!!!");
        }
    }

3. 定义Duck超类

    /**
     * 鸭子超类
     * 需求一: 需要会飞的鸭子。
     *      会飞这个动作并不是所有鸭子都具备的行为。如果在超类上加上新的行为,将会使得某些子类具有这个不恰当的行为。
     *   方式一: 定义新的接口。让需要的这种鸭子行为的类实现它。但这不是一个好的办法。每当新增一个鸭子的种类,都要去实现这个接口。×
     *   如果每次新的需求一来,都会变化到某方面的代码,那么你可以确定,这部分的代码需要被抽取出来。
     *   方式二: 为要要区分开 变化和不变化的部分。准备再组类。一是fly相关,一是quack相关
     *   当我们创建特定对象的时候,指定特定的类型的飞行行为给它。我们需要在鸭子超类中包含设定行为了方法。就可以在运行时动态地改变子类的飞行行为
     */
    public abstract class Duck {
        FlyBehavior flyBehavior;
        QuackBehavior quackBehavior;
    
        /**
         * 外观不同,定义抽象方法让子类实现
         */
        public abstract void display();
    
        /**
         * 呱呱叫
         */
        public void quack() {
            System.out.println("所有鸭子都会: 呱呱叫!!!");
        }
        /**
         * 游泳
         */
        public void swim() {
            System.out.println("所有鸭子都会: 游泳!!!");
        }
    
        public void performFly() {
            flyBehavior.fly();
        }
    
        public void performQuack() {
            quackBehavior.quack();
        }
    
    }

4. 定义Duck实现类

    /**
     * 绿头鸭子
     */
    public class MallardDuck extends Duck{
        public MallardDuck() {
            quackBehavior = new Quack();
            flyBehavior = new FlyWithWings();
        }
        public void display() {
            System.out.printf("mallard duck.");
        }
    }
    
    /**
    * 闲鱼鸭
    */
    public class NothingDuck extends Duck {
        @Override
        public void display() {
            System.out.println("Nothing duck");
        }
    }

5. Main

    /**
     * 不再把鸭子的行为说成「一组行为」,我们开始把行为想成是「一族算法」
     * 鸭子的行为不是继承而来,而是和适当的行为对象组合而来。
     * 设计原则: 多用组合,少用继承。
     */
    public class T {
        public static void main(String[] args) {
            MallardDuck mallardDuck = new MallardDuck();
            mallardDuck.performFly();
            mallardDuck.performQuack();
    
            System.out.println("--------Nothing Duck--------------");
            NothingDuck nothingDuck = new NothingDuck();
            nothingDuck.performFly();
        }
    }

总结

  1. 针对接口编程,而不是针对实现编程
  2. 无论何时你需要修改某个行为,你必须得往下追踪并修改每一个定义此行为的类,一不小心,可能造成新的错误。换句话说,如果每次新的需求一来,都会变化到某方面的代码,那么你就可以确定,这部分的代码需要被抽出来,和其他闻风不动的代码有所区隔。
  3. 把会变化的部分取出并封装起来,以便以后可以轻易地扩充此部分,而不影响不需要变化的其他部分。
  4. 针对接口编程,关键就在多态。利用多态,程序可以针对超类编程,执行时会根据实际状况执行到真正的行为,不会被绑死在超类的行为上。
  5. 策略模式提供了对"开闭原则"完美支持,用户可以在不修改原有系统的基础上选择算法行为,也可以灵活的增加新的算法行为。
  6. 策略模式提供了可替换继承关系的办法。继承使得动态改变算法或行为变得不可能。但是使用策略模式可以实现解耦。
  7. 策略模式缺点是使用者必须知道所有策略类,并自行决定使用哪一个策略类。也就意味着使用者必须理解这些算法之间的区别,以便在恰当的时机选择恰当的算法类。
阅读全文