设计模式(一) - 策略模式(Strategy pattern)

策略模式是什么?

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

为什么使用策略模式?

优点:

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

缺点:
  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
  • 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。

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

使用示例:
该示例取自一个动作冒险游戏,我们将看到代表游戏角色的类和角色可以使用的武器行为的类。每个角色一次只能
使用一种武器。 但是可以在游戏的过程中切换武器。
1.画UML图:

2.UML图分析:
Character(角色)是抽象类,由具体的角色来继承。而Weapon(武器)是接口,由具体的武器来继承。所有实际
的角色和武器都是 具体类。
任何角色如果想换武器,可以调用setWeapon()方法,此方法定义在Character超类中。可用getWeapon()方法来
获取所使用的武器, 来攻击其他角色。
3.代码示例:
Base包:
/**
 * 角色基类
 */
public abstract class Character {
    private WeaponBehavior weapon;

    public abstract void fight();

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

    public void getWeapon() {
        weapon.useWeapon();
    }
}

/**
 * 武器接口
 */
public interface WeaponBehavior {
    void useWeapon();
}

derive包:
角色role包:
/**
 * 国王
 */
public class King extends Character {
    @Override
    public void fight() {
        System.out.println("I'm the King.My attract force is 95.");
    }
}
/**
 * 皇后
 */
public class Queen extends Character {
    @Override
    public void fight() {
        System.out.println("I'm the Queen.My attract force is 90.");
    }
}
/**
 * 骑士
 */
public class Knight extends Character {
    @Override
    public void fight() {
        System.out.println("I'm the Knight.My attract force is 92.5.");
    }
}
/**
 * 妖怪
 */
public class Troll extends Character {
    @Override
    public void fight() {
        System.out.println("I'm the troll.My attract force is 100.");
    }
}
behavior包:
/**
 * 实现用宝剑挥舞
 */
public class SwordBehavior implements WeaponBehavior{
    @Override
    public void useWeapon() {
        System.out.println("I use the sword to attract.");
    }
}
/**
 * 实现用匕首刺杀
 */
public class KnifeBehavior implements WeaponBehavior {
    @Override
    public void useWeapon() {
        System.out.println("I use the knife to attract.");
    }
}
/**
 * 实现用弓箭射击
 */
public class BowAndArrowBehavior implements WeaponBehavior {
    @Override
    public void useWeapon() {
        System.out.println("I use the bow and arrow to attract.");
    }
}
/**
 * 实现用斧头劈砍
 */
public class AxeBehavior implements WeaponBehavior {
    @Override
    public void useWeapon() {
        System.out.println("I use the axe to attract.");
    }
}
测试类:
public class TestTroll {
    public static void main(String[] args) {
        Character character = new Troll();
        character.setWeapon(new AxeBehavior());
        character.fight();
        character.getWeapon();
    }
}
测试结果:
I'm the troll.My attract force is 100.
I use the axe to attract.
总结:
1.我们在实例化Troll(妖怪)对象的时候,没有采用Troll troll = new Troll();这样的方式,这是因为我们用到了一个
设计原则 针对接口编程,而不是针对实现编程。
“针对接口编程”的关键在于多态。利用多态,程序可以针对超类型编程,执行时会根据实际状况执行到真正的行为,
不会被绑死再超类型的行为上。“针对超类性编程”这句话,可以更明确地说成“变量的声明类型应该是超类型,
通常是一个抽象类或者是一个接口,如此,只要是具体实现此超类型的类所产生的对象,都可以指定给这个变量。
这也意味着,声明类时不用理会以后执行时的真正对象类型!”
例如:
Dog d = new Dog();
d.bark();
就是明显的“针对实现编程”,声明变量“d”为Dog类型(是Animal的具体实现),会造成 我们必须针对具体实现编码。
但是“针对接口/超类型编程”做法会如下:
Animal animal = new Dog();
animal.makeSound();
我们知道该对象是狗,但是我们现在利用animal进行多态的调用。
更棒的是,子类实例化的动作不再需要在代码中硬编码,例如 new Dog(),而是“在运行时才指定具体实现的对象”。
animal = getAnimal();
animal.makeSound();
我们不知道实际的子类型是“什么”... 我们只关心它知道如何正确地进行makeSound()的动作就够了。
Character基类中的武器类型声明为接口类型WeaponBehavior也是基于这个设计原则设计的。
2.第二个 设计原则:找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码
混在一起。
在这个小程序demo中,因为每个角色都会“战斗”,所以fight相关的是写在基类中待继承的,它是不会变化的,
每个角色所使用的“武器”都是不同的,故武器是会变化的,所以为了分开“变化和不会变化的部分”,我们把“武器”
从Character类中取出来,建立一组新类来代表每个行为。
而我们以WeaponBehavior为接口,然后使用SwordBehavior等实现类来实现这个父接口,这样的设计,可以让
“武器”的动作被其他的对象复用,因为这些行为已经与角色类无关了。这样一来,有了继承的“复用”的好处,却没
有继承所带来的包袱(比如高耦合),而我们可以新增一些行为,不会影响到既有的行为类,也不会影响“使用”到
武器行为的角色类。
3.第三个 设计原则:多用组合,少用继承。
我们描述事情的方式也稍有改变。不再把角色的行为说成是“一组行为”,我们开始把行为想成是“一族算法”。
想象下,我们后期有一个需求变更,需要给每个角色增加一个“宠物”,可以用来帮助战斗,每个人的宠物自然也会
可能是不同的,我们可以说每个角色“有一个”武器和宠物。“有一个”关系相当有趣:每一个角色都有一个
WeaponBehavior和PetBehavior,好将武器和宠物委托给它们代为处理。
当我们将两个类结合起来使用,如同本例一样,这就是组合(composition)。
这种做法和“继承”不同的地方在于,角色的行为不是继承来的,而是和适当的行为对象“组合”来的。这使我们所
建立的系统具有很大的弹性,不仅可将算法族封装成类,还可以“在运行时动态地改变行为”,只要组合的行为对象
符合正确的接口标准即可。

良好的OO设计必须具备可复用、可扩充、可维护三个特性。

ps:第一篇博客介绍策略模式显的有点啰嗦和长篇大论了点,但细读下来相信你会有所收获的~感谢阅读。

猜你喜欢

转载自blog.csdn.net/siny_chan/article/details/79056928