[设计模式] 策略模式

Posted1 by 呼延十 on January 6, 2019 Hot:

前言

这是我的第一篇读书笔记.

今天拿起了《Head First 设计模式》,读完了第一章”设计模式入门”,这篇博客用来记录对这一章的理解.

首先吹一波这本书,他确实成功的让我没有烦躁,安静的读并且思考了下来.这可能得益于里面大量的插图,以及时不时的提问,让我比较有参与感.此外偶尔会有一些”幽默”的元素穿插在里面,虽然不太好笑,但是总归是一些趣味.

第一章主要是引导用户一步一步设计一个简单的系统,在系统的一步步优化过程中,使用了策略模式,来让系统变得更好.

文中举例是”鸭子应用”,在这里我会其中的”设计谜题”提到的冒险游戏来展开,一步步记录策略模式.

首先上一些理论性的东西.

定义

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

这话听着又是很拗口,看完下面的例子就会明白了.

类图

图来自于维基百科

主要有三个类:

  1. 客户,即使用”策略”的人.
  2. 抽象的策略类主要是定义策略.
  3. 具体的实现策略,可以有多种不同的策略实现.

具体场景

现在我们来设计一个系统,动作冒险游戏.

要求有:

  1. 有多个角色
  2. 角色有武器.每一个角色只能使用一个武器攻击,但是可以替换(就是经常玩游戏时的切换武器啦).

好,简单的想了一下,我们着手开始设计了.

第一版本

我们粗略的设计了一下,Character是个父类,有属性-名字,和方法-攻击.

骑士皇后继承自Character,分别实现了attack().

看起来很美好,这个时候我们发现,不能只会攻击啊!加上防守!

一想,这简单,直接在Character类里面加上一个defense()方法就好了,多简单.

但是有个问题,我们的骑士是个愣头青,不会防守,他相信进攻是最好的防守.

这个时候问题就来了,如果给Character里面加上防守方法,会让子类获得不属于他的能力,在每个子类里面单独加防守的方法,是不科学的.

那么就想到了面向接口编程了,我们把攻击和防守定义为接口,你有啥能力就实现哪个接口呗.

试试看.

第二版本

在这个版本中,我们将攻击防御定义为了两个接口,可以自己选择是否实现.

这样设计已经不错了,,,也是日常编码中较为常用的一种,因为简单方便.

但是仍然有一个问题,那就是在某些情况下重复代码较多.

比如:骑士武士大将军都用剑进行攻击,攻击一模一样,这时候相同的代码需要写三份.

第三版本

图中由于偷懒,AttackAble的子类只画了一个

思想就是,攻击有多个子类,用剑,用刀,用爪子.

然后各类角色分类去实现.

这样子也有一个问题,没有办法实现中途换武器,也就是说,如果骑士实现了用剑,那么他这辈子只能用剑了.不符合要求啊.

这个时候就要用到多态了.

即:声明变量的时候,只声明为超类,这样不用关心具体的实现,直接调用超类方法即可.

第四版本

在这个版本中,将攻击能力Attackable抽象为武器(Weapon),防守能力Defensable抽象为Armor

在这个设计里,每一个Character持有一个武器一个防具,在想要攻击时,调用自己武器的攻击方法,想要防守时,调用自己武器的防守方法.而不用关心持有的是什么武器,以怎样的方式去攻击.

接下来用代码来粗略的实现这一个设计.

实现代码

Character类:

package com.huyan.demo.strategy;

/**
 * created by huyanshi on 2019/1/7
 */
public class Character {

  String name;

  private Weapon weapon;

  public Character() {
    weapon = new Sword();
  }

  public void attack() {
    weapon.attack();
  }

  public void setWeapon(Weapon weapon) {
    this.weapon = weapon;
  }

}

Weapon接口:

package com.huyan.demo.strategy;

/**
 * created by huyanshi on 2019/1/7
 */
public interface Weapon {

  void attack();
}

Sword类:

package com.huyan.demo.strategy;

/**
 * created by huyanshi on 2019/1/7
 */
public class Sword implements Weapon {


  @Override
  public void attack() {
    System.out.println("I'm using a sword!");
  }
}

Spear类:

package com.huyan.demo.strategy;

/**
 * created by huyanshi on 2019/1/7
 */
public class Spear implements Weapon {

  @Override
  public void attack() {
    System.out.println("I'm using a Spear!");
  }
}

最后是Knight类:

package com.huyan.demo.strategy;

/**
 * created by huyanshi on 2019/1/7
 */
public class Knight extends Character {

  public Knight (){
    this.name = "Knight";
  }

  @Override
  public void attack() {
    System.out.print("I'm a knight. -----");
    super.attack();
  }
}

测试代码:

@Test
public void test(){

  Knight knight = new Knight();
  knight.attack();
  knight.setWeapon(new Spear());
  knight.attack();

}

输出结果:

可以看到,在测试代码中,我们首先new了一个骑士,然后调用他的攻击方法,由于在Character中我们设置的默认武器为Sword,因此打印了knight使用sword攻击,之后我们调用setWeapon()方法,给骑士更换了武器,换为了长矛Spear,再次调用attack(),打印了knight使用spear进行了攻击.

策略模式的优缺点

优点

  • 提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
  • 提供了管理相关的算法族的办法。
  • 提供了可以替换继承关系的办法。
  • 可以避免使用多重条件转移语句。

缺点

  • 客户必须知道所有的策略类,并自行决定使用哪一个策略类。
  • 策略模式将造成产生很多策略类

适用环境

在以下情况下可以使用策略模式:

  • 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
  • 一个系统需要动态地在几种算法中选择一种。
  • 如果一个对象有很多的行为,使用多重的条件选择语句来实现。
  • 不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法和相关的数据结构,提高算法的保密性与安全性。

总结

一句话总结策略模式:

准备一组算法,并将每一个算法封装起来,方便客户端调用,替换,新增

一点小思考

最近工作上在做一些和推荐相关的事情,推荐嘛,不上线之前谁也不知道效果是好还是坏,我们是否可以提供多种的推荐算法,入参为用户属性以及所有item,出参为排序好的若干个item,然后使用策略模式,在线上使用时,动态的切换几种推荐算法,并分别记录与之对应的用户留存以及平均停留时长等指标,用来判断哪个算法更加靠谱一些?

完。





ChangeLog

2019-01-06 完成

以上皆为个人所思所得,如有错误欢迎评论区指正。

欢迎转载,烦请署名并保留原文链接。

联系邮箱:huyanshi2580@gmail.com

更多学习笔记见个人博客——>呼延十