针对超类型编程:策略模式

Simple Demo

假如我们设计一款RPG游戏,里面有各种职阶的角色可以选择:剑士、弓箭手、枪兵、骑师等。

该游戏内部设计使用了标准的面向对象技术,设计了一个角色超类,并让各种职阶角色继承该超类。子类先以剑士、枪兵为例。

针对超类型编程:策略模式

现在突然有了一个需求:在玩家有一段时间没有操作游戏角色后,游戏角色可以在等待时哼唱本游戏的主题曲,可以简单理解为让游戏角色唱歌。

1、使用继承解决

对于一个面向对象的程序,这很简单。只要在 Character 类上加上 sing()方法,这样所有职阶的角色类都会继承 sing()。

针对超类型编程:策略模式

但是发生了一个问题,(作为理性被夺走的补偿全能力强化的)狂战士,不存在理性和人格,自然也不可能会唱歌,这样破坏了设定,并不是所有的职阶角色都能唱歌。

虽然可以将sing()方法覆盖掉,在方法体内什么都不做。

针对超类型编程:策略模式

为了“复用”而使用继承,但这样以后每当有新职阶的角色类出现,就要被迫检查该角色是否能够有唱歌这个行为,并可能需要覆盖sing()。

2、那么换成用接口又如何?

将 sing()从超类中取出来,放进一个“ Singable 接口”中,只有会唱歌的职阶角色才实现此接口。

针对超类型编程:策略模式

但这样每个会唱歌的角色都要实现sing()方法,会造成重复代码变多,代码无法复用,而且角色哼唱的方式可能还有多种变化。

3、使用策略模式

①先找出应用中可能需要变化之处,将会变化的部分取出封装起来,让该部分改变不会影响其他部分

我们知道 Character 类内的 sing()会随着职阶角色的不同而改变,将其从 Character 类中取出来,建立一组新的类来代表这个唱歌行为。

②如何实现唱歌的行为的类呢?针对接口编程

我们利用接口 SingBehavior 代表这个行为,并有一些具体实现。

类图:

针对超类型编程:策略模式

实际代码:

public interface SingBehavior {//行为接口
    void sing();
}
public class SingSoftly implements SingBehavior { // 轻轻唱
    @Override
    public void sing() {
        System.out.println("hum hum hum ~ ~");
    }
}
public class SingNoWay implements SingBehavior { // 没法唱
    @Override
    public void sing() {
        System.out.println("......"); // 沉默
    }
}

针对接口编程是为了针对超类型(一个抽象类或者是一个接口)编程,将变量声明为超类型后,其具体实现类的对象都可以指定给这个变量,根据具体实现执行实际行为。

声明了SingBehavior(超类型)变量后,我们甚至不需要知道实际的子类型,只关心它能够进行正确的sing()的行为就够了。

这样将其分离出来后,可以让唱歌的行为被其他对象复用,这个行为已经和 Character 类无关了。

③将 SingBehavior 和 Character 组合起来(多用组合,少用继承)。

在 Character 类中加入一个 SingBehavior 实例变量,每个角色对象都会动态的设置这个变量,在运行时就可以引用正确的行为类。

public abstract class Character {
    protected SingBehavior singBehavior;

    public Character{
        singBehavior = new SingSoftly();//默认能够轻轻唱
    }
    public void fight() {// 进行战斗
        System.out.println("I can fight");
    }

    public abstract void capability();

    public void performSing() { 
        singBehavior.sing();
    }
    
    public void setSingBehavior(SingBehavior singBehavior) { //用于动态设定唱歌行为
        this.singBehavior = singBehavior;
    }
    // 其他方法......
}

这样想要进行唱歌的动作,只需委托给 singBehavior 去唱歌就可以了,不需要知道 singBehavior 真正引用的对象到底是什么。

下面看三个具体实现类:

Character 已经在构造器里初始化了 singBehavior,所以能够唱歌的 Saber 类和 Lancer 类不需要再初始化该变量。

public class Saber extends Character {
    @Override
    public void capability() {
        System.out.println("I can use the sword");// 使用剑
    }
}
public class Lancer extends Character {
    @Override
    public void capability() {
        System.out.println("I can use the lance");// 使用长矛
    }
}
public class Berserker extends Character {
    public Berserker() {
        singBehavior = new SingNoWay(); //默认沉默无声
    }

    @Override
    public void capability() {
        System.out.println("I can destroy everything");// 破坏一切
    }
}

测试代码:

public class CharacterSingTest {
    public static void main(String[] args) {
        Character saber = new Saber();
        System.out.println("It's Saber");
        saber.performSing();
        Character berserker = new Berserker();
        System.out.println("It's Berserker");
        berserker.performSing(); // Berserker默认沉默无声
        //一开始Berserker的设定是失去理性的,假设有办法让他短暂的恢复理性
        berserker.setSingBehavior(new SingBehavior() {    //动态的设置 singBehavior
            @Override
            public void sing() {
                // 短暂的恢复了理性,能够唱歌了
                System.out.println("I come to my senses temporarily , hum hum hum ~ ~");
            }
        });
        berserker.performSing();
    }
}

打印结果:

It's Saber
hum hum hum ~ ~
It's Berserker
......
I come to my senses temporarily , hum hum hum ~ ~

重新设计后的类图:

针对超类型编程:策略模式

重新设计后的类结构:所有职阶的角色类继承 Character,具体唱歌行为实现 SingBehavior接口,后将CharacterSingBehavior组合起来,委托SingBehavior执行唱歌行为。

这一组唱歌行为可以想象成一个算法族,将每一个算法封装起来,且SingSoftly SingNoWay这些具体算法实现了同一个接口,是可以互换复用的;算法以后发生的变化独立于使用客户(Character)。这就是策略模式。

策略模式类图:

针对超类型编程:策略模式

4JDK中存在的策略模式

JDK中的比较器Comparator就应用了策略模式。

我们使用静态方法Collections.sort()方法给集合排序时,有两个重载方法:

①Collections.sort(List)

要求参数List集合里的元素必须实现Comparable接口(实现compareTo()方法),这样才能根据元素自定义的排序算法进行排序,但每次更换排序方式时都需要去修改compareTo()方法。

②Collections.sort(List, Comparator)

不要求List集合里的元素实现了Comparable接口,排序时会根据Comparator的具体实现算法进行排序。

这里的Collections.sort(List, Comparator)方法就是Context(环境角色)ComparatorStrategy(抽象的策略),而真正传入参数需要的是实现了Comparator具体实现类(具体策略类)

这样每次更换排序方式时只需要替换具体的比较器类,而不需要再去修改集合里的元素类。

参考书籍:《Head First 设计模式》

相关推荐