设计模式篇之——策略设计模式

Posted 风清扬逍遥子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式篇之——策略设计模式相关的知识,希望对你有一定的参考价值。

        策略模式在实际开发过程中其实非常的常见,那么本章也从原始需求扩展到设计模式上是如何更好的落地的。

1、定义

        分别封装行为接口,实现算法族,父类里放行为接口对象,在子类里具体设定行为对象。一个类的行为或其算法可以在运行时更改,这种类型的设计模式属于行为型模式。

        定义总是模糊的,用大白话来说,我们把每个要做的事情称为【行为】,每一个行为都应该被抽象成一个一个接口,和这些行为的使用者进行解耦。举个例子,比如鸭子这个抽象父类存在很多的行为方法,鸭子会游泳,会叫,会很多本领;每个不同的鸭子都要去继承这个抽象鸭子父类,每个鸭子叫声不同,需要对父类的叫方法进行重写。看起来是没问题,如果增加了额外的需求,比如鸭子要飞,那么很可能所有的子类都要去必须要重写飞,但是不是所有鸭子都会飞。一旦鸭子种类多起来,改动就会很大,我们需要的效果是,具体要哪个鸭子做什么,不应该所有的鸭子都被影响。

        既然很离谱,那么我们分析下,变动的东西是不是一些特性?比如飞,不是所有的鸭子都会飞,不是所有的鸭子都会跳舞(比如唐老鸭),我们假定每个鸭子都会游泳。那么抽离出来个性,不变的是共性,即抽象是共性,接口是特性

        灵魂画师又来了:

2、案例讲解

        组成部分我就不说了,上面的图就表明了哪些组成部分,正式进入需求:

        产品经理:那个老王,你帮我做个功能,我要两个鸭子,黑鸭和红鸭子,这两个鸭子会叫、会飞、会游泳哈,区别是黑鸭和红鸭子的颜色不一样(这不是废话么)。

        这个时候我们用OOP思想(面向对象编程),定义一个鸭子父类。

/**
 * @Description: 设计一个抽象类鸭子
 * @title: Duck
 * @Author Star_Chen
 * @Date: 2021/12/21 22:36
 * @Version 1.0
 */
public abstract class Duck 

    public Duck() 
    

    /**
     * @Date: 2021/12/21 22:39
     * @Description: 抽象的自我介绍颜色方法让子类实现
     */
    public abstract void perform();

    /**
     * @Date: 2021/12/21 22:39
     * @Description: 本抽象父类中自己实现游泳方法,子类也可以自己实现
     */
    public void swim()
        System.out.println("我是个鸭子,会游泳~");
    

    /**
     * @Date: 2021/12/21 22:46
     * @Description: 本抽象父类中自己实现叫的方法,子类也可以自己实现
     */
    public void quack()
        System.out.println("我是鸭子,我会叫呱呱呱!");
    

        来一个红鸭子

/**
 * @Description: 红鸭子
 * @title: RedDuck
 * @Author Star_Chen
 * @Date: 2021/12/21 22:40
 * @Version 1.0
 */
public class RedDuck extends Duck 

    @Override
    public void perform() 
        System.out.println("我是红色的鸭子!");
    

        再来一个黑鸭子

/**
 * @Description: 黑鸭子
 * @title: BlackDuck
 * @Author Star_Chen
 * @Date: 2021/12/21 22:41
 * @Version 1.0
 */
public class BlackDuck extends Duck 

    @Override
    public void perform() 
        System.out.println("我是黑色的鸭子!");
    

        来个Main函数调用

/**
 * @Description:
 * @title: DuckMain
 * @Author Star_Chen
 * @Date: 2021/12/21 22:44
 * @Version 1.0
 */
public class DuckMain 

    public static void main(String[] args) 

        RedDuck redDuck = new RedDuck();
        BlackDuck blackDuck = new BlackDuck();

        redDuck.quack();
        redDuck.perform();
        redDuck.swim();
        System.out.println("<==========================>");
        blackDuck.quack();
        blackDuck.perform();
        blackDuck.swim();

    

        输出结果很简单

我是鸭子,我会叫呱呱呱!
我是红色的鸭子!
我是个鸭子,会游泳~
<==========================>
我是鸭子,我会叫呱呱呱!
我是黑鸭子!
我是个鸭子,会游泳~

        这个时候产品经理说,对了给我再来两个鸭子,让鸭子飞,但是呢我只让黑鸭子飞和xxx鸭子飞,对了我还要不同的鸭子叫的不一样,红鸭子geigeigei,黑鸭子一给我里giaogiao...

        想一下,如果在父类写fly方法,定义成抽象方法,那么所有继承父类的鸭子,都要添加fly方法,但是不是所有鸭子都会飞呀,违背逻辑。那如果不定义成抽象方法,定义成父类的普通方法,子类的所有鸭子,要想实现就必须重写父类的fly,或者要么就调用父类的fly,但是实际中,比如北京烤鸭都没有飞的概念,怎么能让子类具有这样的方法呢?显然这是不可能的

        父类挖的一个坑,每个子类都要来填,增加工作量,复杂度O(N^2)。不是好的设计方式。这时候,我们应该考虑哈继承的弊端了。使用接口就可以很好的解决这个问题。(思路:继承是实现共性,减少代码的重复。接口是实现特性。

        我们采用策略模式来做

        分析:

        1、项目变化与不变部分,提取变化部分,抽象成接口+实现;(抽象是共性,接口是特性)

        2、鸭子那些功能是会根据新需求变化的?叫声、飞行

        定义两个变化的行为接口

        FlyBehavior和QuackBehavior

        定义飞的行为

/**
 * @Description:
 * @title: FlyBehavior
 * @Author Star_Chen
 * @Date: 2021/12/21 23:53
 * @Version 1.0
 */
public interface FlyBehavior 

    /**
     * @Date: 2021/12/21 23:54
     * @Description: 飞的行为
     */
    void fly();

        定义叫的行为

/**
 * @Description:
 * @title: QuackBehavior
 * @Author Star_Chen
 * @Date: 2021/12/22 0:00
 * @Version 1.0
 */
public interface QuackBehavior 

    /**
     * @Date: 2021/12/22 0:01
     * @Description: 叫的行为
     */
    void quack();

        那么Duck父类我们就要调整,只要涉及到个性的东西,比如fly,quack,都要以行为去做变更。

/**
 * @Description: 设计一个抽象类鸭子
 * @title: Duck
 * @Author Star_Chen
 * @Date: 2021/12/21 22:36
 * @Version 1.0
 */
public abstract class Duck 

    //具备抽象行为
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;

    /**
     * @Date: 2021/12/22 0:07
     * @Description: 实例化对象时可以动态的改变对象的行为(比继承灵活性强)
     */
    public void setFlyBehavior(FlyBehavior flyBehavior) 
        this.flyBehavior = flyBehavior;
    

    /**
     * @Date: 2021/12/22 0:07
     * @Description: 实例化对象时可以动态的改变对象的行为(比继承灵活性强)
     */
    public void setQuackBehavior(QuackBehavior quackBehavior) 
        this.quackBehavior = quackBehavior;
    

    /**
     * @Date: 2021/12/21 22:46
     * @Description: 本抽象父类中自己实现叫的方法,子类也可以自己实现
     */
    public void quack()
//        System.out.println("我是鸭子,我会叫呱呱呱!");
        quackBehavior.quack();
    
    
    public void fly()
        flyBehavior.fly();
    

    /**
     * @Date: 2021/12/21 22:39
     * @Description: 抽象的自我介绍颜色方法让子类实现
     */
    public abstract void perform();

    /**
     * @Date: 2021/12/21 22:39
     * @Description: 本抽象父类中自己实现游泳方法,子类也可以自己实现
     */
    public void swim()
        System.out.println("我是个鸭子,会游泳~");
    

        叫行为的实现类

/**
 * @Description: giaogiao的叫
 * @title: GiaoQuackBehavior
 * @Author Star_Chen
 * @Date: 2021/12/22 0:01
 * @Version 1.0
 */
public class GiaoQuackBehavior implements QuackBehavior 
    @Override
    public void quack() 
        System.out.println("一给我里giaogiao的叫!");
    


/**
 * @Description: geigei的叫
 * @title: UglyQuackBehavior
 * @Author Star_Chen
 * @Date: 2021/12/22 0:01
 * @Version 1.0
 */
public class UglyQuackBehavior implements QuackBehavior 
    @Override
    public void quack() 
        System.out.println("geigei的叫!");
    

        飞行为的实现类

/**
 * @Description: 低飞
 * @title: LowFlyBehavior
 * @Author Star_Chen
 * @Date: 2021/12/21 23:55
 * @Version 1.0
 */
public class LowFlyBehavior implements FlyBehavior 
    @Override
    public void fly() 
        System.out.println("鸭子会低飞!");
    


/**
 * @Description: 高飞
 * @title: HighFlyBehavior
 * @Author Star_Chen
 * @Date: 2021/12/21 23:54
 * @Version 1.0
 */
public class HighFlyBehavior implements FlyBehavior 
    @Override
    public void fly() 
        System.out.println("鸭子会高飞!");
    

        我们模拟鸭子主函数,还是实现上面新增的需求:

        红鸭子叫geigeigei,黑鸭子叫一给我里giaogiao

        红鸭子高飞,黑鸭子低飞

        在调用方面我们就这么写:

/**
 * @Description:
 * @title: DuckMain
 * @Author Star_Chen
 * @Date: 2021/12/21 22:44
 * @Version 1.0
 */
public class DuckMain 

    public static void main(String[] args) 


        //父类为Duck,屏蔽了超类的差别性
        Duck redDuck = new RedDuck();
        Duck blackDuck = new BlackDuck();

        redDuck.perform();
        redDuck.fly();

        System.out.println("<==========================>");

        blackDuck.perform();
        blackDuck.quack();

    

        输出结果:

我是红色的鸭子!
鸭子会高飞!
<==========================>
我是黑色的鸭子!
一给我里giaogiao的叫!

        如果我想自定义在对象的实例化的时候同时对行为进行重写,那么可以通过setBehavior来实现。

/**
 * @Description:
 * @title: DuckMain
 * @Author Star_Chen
 * @Date: 2021/12/21 22:44
 * @Version 1.0
 */
public class DuckMain 

    public static void main(String[] args) 

        //父类为Duck,屏蔽了超类的差别性
        Duck redDuck = new RedDuck();
        Duck blackDuck = new BlackDuck();

        redDuck.perform();
        redDuck.setFlyBehavior(new FlyBehavior()

            @Override
            public void fly() 
                System.out.println("红鸭子我不飞了");
            
        );
        redDuck.fly();

        System.out.println("<==========================>");

        blackDuck.perform();
        blackDuck.setQuackBehavior(new QuackBehavior() 
            @Override
            public void quack() 
                System.out.println("黑鸭子我不叫了");
            
        );
        blackDuck.quack();
    

        以上代码都是java中的基础知识。运用设计模式中的策略模式。把变化的部分提取出来变为接口+实现。其中,main方法中,父类为Dock基类,是为了屏蔽子类的超类Dock的差别性。Duck类中的SetQuackBehavoir()方法,灵活的让实例化对象灵活的改变对象的行为。比如,绿头鸭,使用了SetQuackBehavoir()方法,定制了自己的quck()方法。因为不是每只鸭都能叫的,叫的是当前鸭的特性。

3、总结

        我们可以假想,一个鸭子可以有很多子类。如果把方法全部写在父类。子类都会继承它,对于不同的子类,有些方法违反了逻辑成为了冗余(不应该出现在子类中)。

        那是不是就没有缺点了呢?当然有,在调用端必须要知道所有的这些行为,否则无法在任何场合决定调用行为的什么结果,而对于很多场景需求中,我们并不明确的知道下一个需求是什么,即不知道会存在多少个行为策略类,因为每个行为策略都会有个类出现,如果被多处调用,就必须要知道所有的策略场景,维护也不是很方便。

        下一章节我们讲述装饰者模式。

以上是关于设计模式篇之——策略设计模式的主要内容,如果未能解决你的问题,请参考以下文章

设计模式篇之——策略设计模式

设计模式篇之——策略设计模式

编程技巧篇之线程上下文

策略模式-设计模式

编程技巧篇之线程上下文

设计模式篇之——命令设计模式