策略模式和工厂模式的区别_设计模式系列—策略模式

4a08557e733930cf877cf121ab006465.png

作者公众号:一角钱技术(org_yijiaoqian)

前言

  • 23种设计模式速记
  • 单例(singleton)模式
  • 工厂方法(factory method)模式
  • 抽象工厂(abstract factory)模式
  • 建造者/构建器(builder)模式
  • 原型(prototype)模式
  • 享元(flyweight)模式
  • 外观(facade)模式
  • 适配器(adapter)模式
  • 装饰(decorator)模式
  • 观察者(observer)模式
  • 持续更新中......

23种设计模式快速记忆的请看上面第一篇,本篇和大家一起来学习策略模式相关内容。

9b0a514e0ef1b60d042ace40621215b4.png

模式定义

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

0a48242e9b495a3fe21e489d4e975f7b.png

策略模式中体现了几个设计原则: ①把变化的代码从不变的代码中分离出来; ②针对接口编程而不是具体类; ③多用组合/聚合,少用继承,策略模式中Context通过聚合使用策略。

解决的问题

在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护,使用策略模式将算法的责任和本身进行解耦。

模式组成

组成(角色)作用抽象策略角色(Strategy)定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。具体策略角色(ConcreteStrategy)实现了抽象策略定义的接口,提供具体的算法实现。环境类(Context)持有一个策略类的应用,最终供客户端调用

实例说明

《植物大战僵尸》这个游戏很多人都玩过,里面有各种不同的植物和僵尸。不同的植物、僵尸各自有不同的特点。假如你要开发这样一款游戏,游戏最开始的版本比较简单,只有两种僵尸:普通僵尸、旗手僵尸。

第一版

类型外观移动攻击普通僵尸普通朝着一个方向移动咬旗手僵尸普通+手持旗子朝着一个方向移动咬

c60be3240a062e9de4cda39456889207.png

步骤1:定义抽象策略角色,抽象类

abstract class AbstractZombie{    public abstract void display();    public void attack(){        System.out.println("咬");    }    public void move(){        System.out.println("一步一步移动");    }}

步骤2:定义具体的策略角色,普通僵尸

class NormalZombie extends AbstractZombie{    @Override    public void display() {        System.out.println("我是普通僵尸");    }}

步骤3:定义具体的策略角色,旗手僵尸

class FlagZombie extends AbstractZombie{    @Override    public void display() {        System.out.println("我是旗手僵尸");    }}

步骤4:测试

public class StrategyPattern {    public static void main(String[] args) {        AbstractZombie normalZombie = new NormalZombie();        AbstractZombie flagZombie = new FlagZombie();        flagZombie.display();        flagZombie.move();        flagZombie.attack();        System.out.println("---------------");        normalZombie.display();        normalZombie.move();        normalZombie.attack();    }}

输出结果

我是旗手僵尸一步一步移动咬---------------我是普通僵尸一步一步移动咬

完美!游戏可以上线了。

但是没过多久你发现你开发的这个游戏玩家越来越少,打开评论一看,都在吐槽这个游戏僵尸种类太少,玩了几次就没啥意思了。 这好办,再加几种僵尸呗,于是就有了第二版

第二版

类型外观移动攻击普通僵尸普通朝着一个方向移动咬旗手僵尸普通+手持旗子朝着一个方向移动咬大头僵尸大头朝着一个方向移动头撞石膏僵尸石膏装一瘸一拐武器XXX僵尸.........

1e00ddf765a35bf72258e179bef8f04e.png

这简单,我再写俩僵尸类,然后重写跟抽象僵尸类(AbstractZombie)不一样的方法实现就行。

步骤1:定义具体的策略角色,大头僵尸

class BigZombie extends AbstractZombie {    @Override    public void display() {        System.out.println("我是大头僵尸");    }    @Override    public void attack() {        System.out.println("头撞");    }}

步骤2:定义具体的策略角色,石膏僵尸

class GypsumZombie extends AbstractZombie{    @Override    public void display() {        System.out.println("我是石膏僵尸");    }    @Override    public void move() {        System.out.println("一瘸一拐");    }    @Override    public void attack() {        System.out.println("武器");    }}

OK!第二版上线!

没过多久用户们又玩腻了,用户玩腻了,又要加僵尸了。不过还好你已经得心应手了,不就是各种继承吗。

5956b970313b789aa2f0eaeda08186e9.png

但是只是无脑加僵尸哪够,还有一堆用户吐槽你这游戏的BUG:你这僵尸遇到障碍物都不带停的,遇到植物应该停止移动,开始攻击。所以这些僵尸的各个行为在不同情况下是不一样的,这可咋办,你已经写了一堆僵尸类了,难倒要挨个类加判断逻辑改变行为?这个时候你想到了开闭原则:对扩展开放,对修改关闭。看来你的代码需要重构一下了。

第三版

僵尸的移动方式和攻击方式有不同的实现方式,而且要可以动态改变。先把这两个行为抽取成接口。

步骤1:定义移动行为接口

interface MoveBehavior {    void move();}

步骤2:定义攻击行为接口

interface AttackBehavior {    void attack();}

步骤3:定义抽象策略角色,抽象类

abstract class AbstractZombie {    MoveBehavior moveBehavior;    AttackBehavior attackBehavior;    public AbstractZombie(MoveBehavior moveBehavior, AttackBehavior attackBehavior) {        this.moveBehavior = moveBehavior;        this.attackBehavior = attackBehavior;    }    abstract void display();    void move() {        moveBehavior.move();    }    void attack() {        attackBehavior.attack();    }    public void setAttackBehavior(AttackBehavior attackBehavior) {        this.attackBehavior = attackBehavior;    }    public AttackBehavior getAttackBehavior() {        return attackBehavior;    }    public void setMoveBehavior(MoveBehavior moveBehavior) {        this.moveBehavior = moveBehavior;    }    public MoveBehavior getMoveBehavior() {        return moveBehavior;    }}

步骤4:定义各种僵尸子类

class NormalZombie extends AbstractZombie {    public NormalZombie(MoveBehavior moveBehavior, AttackBehavior attackBehavior) {        super(moveBehavior, attackBehavior);    }    @Override    void display() {        System.out.println("我是普通僵尸");    }}class FlagZombie extends AbstractZombie {    public FlagZombie(MoveBehavior moveBehavior, AttackBehavior attackBehavior) {        super(moveBehavior, attackBehavior);    }    @Override    void display() {        System.out.println("我是旗手僵尸");    }}class BigZombie extends AbstractZombie {    public BigZombie(MoveBehavior moveBehavior, AttackBehavior attackBehavior) {        super(moveBehavior, attackBehavior);    }    @Override    void display() {        System.out.println("我是大头僵尸");    }}class GypsumZombie extends AbstractZombie {    public GypsumZombie(MoveBehavior moveBehavior, AttackBehavior attackBehavior) {        super(moveBehavior, attackBehavior);    }    @Override    void display() {        System.out.println("我是石膏僵尸");    }}

步骤5:定义移动行为子类

class StepByStepMove implements MoveBehavior {    @Override    public void move() {        System.out.println("一步一步移动");    }}class LameMove implements MoveBehavior {    @Override    public void move() {        System.out.println("一瘸一拐");    }}

步骤6:定义攻击行为子类

class BiteAttack implements AttackBehavior{    @Override    public void attack() {        System.out.println("咬");    }}class HeadAttack implements AttackBehavior{    @Override    public void attack() {        System.out.println("头撞");    }}class ArmsAttack implements AttackBehavior{    @Override    public void attack() {        System.out.println("武器");    }}

测试

public class StrategyPattern {    public static void main(String[] args) {        // 普通僵尸        NormalZombie normalZombie = new NormalZombie(new StepByStepMove(), new BiteAttack());        normalZombie.display();        normalZombie.move();        normalZombie.attack();        System.out.println("-----------");        // 旗手僵尸        FlagZombie flagZombie = new FlagZombie(new StepByStepMove(), new BiteAttack());        flagZombie.display();        flagZombie.move();        flagZombie.attack();        System.out.println("-----------");        // 大头僵尸        BigZombie bigZombie = new BigZombie(new StepByStepMove(), new HeadAttack());        bigZombie.display();        bigZombie.move();        bigZombie.attack();        System.out.println("-----------");        // 石膏僵尸        GypsumZombie gypsumZombie = new GypsumZombie(new LameMove(), new BiteAttack());        gypsumZombie.display();        gypsumZombie.move();        //如果石膏僵尸遇到了第一个植物        System.out.println("我遇到了第一个植物");        gypsumZombie.setAttackBehavior(new ArmsAttack());        gypsumZombie.move();        gypsumZombie.attack();    }}

执行结果

我是普通僵尸一步一步移动咬-----------我是旗手僵尸一步一步移动咬-----------我是大头僵尸一步一步移动头撞-----------我是石膏僵尸一瘸一拐我遇到了第一个植物一瘸一拐武器

优点

  1. 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句。
  2. 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
  3. 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
  4. 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
  5. 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。

缺点

  1. 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
  2. 策略模式造成很多的策略类。

应用场景

  1. 当你有很多类似的类,但它们执行某些行为的方式不同时,请使用此策略;
  2. 使用该模式将类的业务逻辑与算法的实现细节隔离开来,这些算法在逻辑上下文中可能不那么重要;
  3. 当你的类具有大量的条件运算符,并且在同一算法的不同变体之间切换时,请使用此模式。

源码中的应用

#JDKjava.util.Comparatorjava.util.concurrent.ThreadPoolExecutor#Springorg.springframework.beans.factory.support.InstantiationStrategy......

比较器Comparator

在Java的集合框架中,经常需要传入一个比较器Comparator用于排序,这使用的就是策略模式。

我们看一个案例

定义一个 Person 类

class Person {    int age;    int height;    public Person(int age, int height) {        this.age = age;        this.height = height;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }    public int getHeight() {        return height;    }    public void setHeight(int height) {        this.height = height;    }    @Override    public String toString() {        return "Person{" +                "age=" + age +                ", height=" + height +                '}';    }}

这Person里有age、height,虽然实现了Comparable接口的compareTo方法,但是这个比较逻辑是不变的,永远是根据年龄排序,哪天你想根据身高排序就要去修改Person里的compareTo方法。

所以我们定义两个比较器:

//策略1 根据年龄排序class SortByAge implements Comparator {    @Override    public int compare(Person o1, Person o2) {        if (o1.getAge() > o2.getAge()) {            return 1;        } else if (o1.getAge()  {    @Override    public int compare(Person o1, Person o2) {        if (o1.getHeight() > o2.getHeight()) {            return 1;        } else if (o1.getHeight() 

测试:

public class ComparatorTest {    public static void main(String[] args) {        Person[] persons =                new Person[]{new Person(10, 111), new Person(18, 99), new Person(15, 122)};        Arrays.sort(persons, new SortByHeight());        print(persons);    }    static void print(Person[] array) {        for (int i = 0; i 

打印结果:

Person{age=18, height=99}Person{age=10, height=111}Person{age=15, height=122}

这里Arrays就是环境角色Context,Comparator就是抽象策略Strategy,两个比较器实现就是具体实现策略ConcreteStrategy

ThreadPoolExecutor中的拒绝策略

在创建线程池时,需要传入拒绝策略,当创建新线程使当前运行的线程数超过maximumPoolSize时,将会使用传入的拒绝策略进行处理。这也是策略模式。

  • AbortPolicy:直接抛出异常
  • CallerRunsPolicy:在任务被拒绝添加后,会调用当前线程池的所在的线程去执行被拒绝的任务
  • DiscardPolicy:不处理,直接丢弃
  • DiscardOldestPolicy:当任务被拒绝添加时,会抛弃任务队列中最旧的任务也就是最先加入队列的,再把这个新任务添加进去

Spring中bean实例化策略

31691591c2086c3c9d287d95011fd46f.png

接口InstantiationStrategy是实例化策略接口类,它定义了三个实例化接口,然后SimpleInstantiationStrategy实现了该策略,它主要做一些简单的根据构造函数实例号bean的工作,然后CglibSubclassingInstantiationStrategy又继承了SimpleInstantiationStrategy新增了方法注入方式根据cglib生成代理类实例化方法。

在AbstractAutowireCapableBeanFactory中管理了该策略的一个对象,默认是CglibSubclassingInstantiationStrategy策略,运行时候可以通过setInstantiationStrategy改变实例化策略,如果你自己写个个策略的话。

PS:以上代码提交在 Github

https://github.com/Niuh-Study/niuh-designpatterns.git

文章持续更新,可以公众号搜一搜「 一角钱技术 」第一时间阅读。 本文 GitHub org_hejianhui/JavaStudy 已经收录,欢迎 Star。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值