• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
逐梦年华~Apache_xiaochao
记录个人IT成长路上的点点滴滴...
博客园    首页    新随笔    联系   管理    订阅  订阅
设计模式——策略模式

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


以一个模拟鸭子的游戏来举例说明:
  • 需求说明

    要求一个池塘里面有很多鸭子,我们可以灵活的添加各种鸭子,这些鸭子有很多属于自己的特征,比如叫声、飞翔等等,总之就是一个池塘里面游满了各种各样的鸭子,而我们的程序可以灵活的添加鸭子到这些池塘,同时这些鸭子还具有属于自己的特征。

  • 系统设计
    利用面向对象的编程思想,我们最容易想到的就是设计一个Duck的父类,其中定义了一些公共的方法和属性,然后让每一个鸭子都去继承这个父类。
    其实这种设计有一个很大的问题:我们很难抽象出所有鸭子的公共行为。对于一只鸭子,拥有的行为可以列举出为:鸣叫、游泳、走路、飞翔,吃饭、睡觉、繁衍等等,这些行为不是所有的鸭子都具备的,因为鸭子只是一个统称,往下细分就会有各种各样的鸭子:真的、假的、会飞的、不会飞的、吃稻谷的、吃虫的.....,直白的说就是,我们如果把某一个行为定义在父类Duck中的话,肯定会出现在某一个导出类中不适用的情况。
    第二种设计方法是为每一种行为都抽象一个接口,某一种鸭子如果有某一种行为就实现这个接口。这种设计的问题在于:①我们可能要在一个类中实现很多个接口,这样的程序不美观;②如果多个鸭子都具备某一种行为,但是这些鸭子不属于同一个种类,那么我们就得在每一个导出类中都实现相同的方法,这样的话代码重复率会很高,同样使得程序不美观。
    这里我们采用策略模式来设计这个系统。策略模式在这里的设计思想可以概括为:我们只搭一个鸭子的框架,然后我们去实现很多种不同的行为,如果我们当前希望构建的鸭子具有某种行为,那么我们就找到已经实现的这种行为,拼装到这个鸭子的骨架上,当我们把所有的这个鸭子应该具有的行为都瓶装好之后,这个鸭子也就成型了。这样的设计是站在所有鸭子的高度之上的,而不是针对具体的某一只鸭子的。相同的功能我们只需要实现一次,接下来就是根据不同的需要来进行拼装,而且如果某一天某只鸭子的行为发生了变化,我们需要做的只是“拆下当前零件,换另外一个零件装上”即可,而不需要对鸭子本身伤筋动骨。
  • 系统实现

上图为鸭子游戏的UML类图,说明如下:
  • 接口
        QuackBehavior:鸣叫行为接口,其中声明了一个方法quack()
        FlyBehavior:飞行行为接口,其中声明了一个方法fly()
  • 接口实现类
        QuackWithGaga:定义了嘎嘎叫的行为方式,鸣叫行为实现类之一
        QuackWithZhizhi:定义了吱吱叫的行为方式,鸣叫行为实现类之一
        QuackWithNull:定义了不会鸣叫的行为方式,鸣叫行为实现类之一
        
        FlyWithWings:定义了用翅膀飞的行为方式,飞行行为实现类之一
        FlyWithNull:定义了不会飞行的行为方式,飞行行为实现类之一
  • 抽象类
        Duck:鸭子抽象类,定义了一个鸭子的骨架,代码如下:

 1 /** 
 2  * 鸭子抽象类
 3  * @author Apache_xiaochao
 4  * @version 2014-8-9 10:51:23
 5  */
 6 public abstract class Duck {
 7  
 8 //这两个域可以看作是行为的装配接口,接口上装配不同的组件来实现同一类行为的不同实现方式
 9  protected FlyBehavior flyBehavior;
10  protected QuackBehavior quackBehavior;
11  
12  /**
13   * 飞行方法
14   */
15  protected void fly(){
16       if(flyBehavior != null)
17            flyBehavior.fly();     //这里是对行为调用的通用框架,具体的表现方式由具体实现而定
18  }
19  
20  /**
21   * 鸣叫方法
22   */
23  protected void quack(){
24       if(quackBehavior != null)
25            quackBehavior.quack();   //这里是对行为调用的通用框架,具体的表现方式由具体实现而定
26  }
27  
28  /**
29   * 显示当前鸭子的特性
30   */
31  public abstract void display();   //抽象方法,由导出类实现来进行特征展示
32 
33 }
  • 导出类
        GeneralDark:普通的鸭子类,Duck的具体化
        RubberDuck:橡皮鸭类,Duck的具体化

 1 以GeneralDark为例,实现代码如下:
 2 
 3 /**
 4  * 普通的鸭子:用翅膀飞,会嘎嘎叫
 5  *
 6  * @author Apache_xiaochao
 7  *
 8  */
 9 public class GeneralDark extends Duck {
10 
11  /*
12   * 在这里完成具体的拼接,将应有的行为装配到骨架上
13   */
14  public GeneralDark() {
15       flyBehavior = new FlyWithWings();
16       quackBehavior = new QuackWithGaga();
17  }
18 
19  @Override
20  public void display() {
21       System.out.println("我是一只普通的鸭子");
22       fly();
23       quack();
24  }
25 
26 }
整个架构中代码执行过程说明:
    父类Duck中定义了一些域和方法,Duck依赖于行为接口,利用面向接口编程的思想,可以在导出类中规定具体的行为方式,我们都知道在继承中,父类的非private修饰的域也是被导出类所拥有的,而且Duck中的域不是static的,这也就是说每一个子类拥有的父类的域都是私有的,不会共享。父类在通过接口已经规定了行为的调用的方式,但是具体调用行为的哪一种实现方式,这是由导出类去自己选择的。这样的父类就是一个骨架,是通用的,而导出类可以根据实际来进行组合,从而可以达到不同的效果。
  • 总结:
  1. 针对接口编程,这里的接口并不仅仅指java中的interface,任何超类型都可以被看做是一个接口,只要他是所有导出类的超类(接口、抽象类、普通类都可以)
  2. 多用组合,少用继承,这样的代码更加灵活,耦合性更小
  3. 封装变化,应该将需要变化的东西封装起来,约定好接口,当变化发生时可以灵活的替换
  4. 良好的OO设计必须具备可复用、可扩充、可维护三个特性

 

posted on 2014-04-01 15:09  Apache_xiaochao  阅读(514)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3