设计模式——策略模式

kuilz 2024-05-10 {System Design} [Design Pattern]

前言

最近闲暇时在看《Head First Design Pattern》这本书,感觉蛮有意思的,不妨记录一下。

叠甲:笔者才疏学浅,水平有限,错漏之处,欢迎指出。

正文

面向对象设计有几个基本特性:抽象、封装、继承和多态。在设计过程中,我们会把一个事物的属性和方法封装起来,成为一个类,如果这些属性和方法是多个类通用的,我们还会把通用的部分单独封装起来,作为父类,从而实现代码复用。但一个类继承父类实现代码复用的同时,也有了一些限制:

  1. 父类有的行为它都会有。
  2. 它的行为不能动态改变。

这样说可能难以理解,不如从我们最先接触面向对象时的例子开始,让我们把记忆拉回到大一大二,看看熟悉的猫吃鱼,狗吃肉,奥特曼爱打小怪兽。

Animal class:

package org.kuilz.StrategyPattern.original;

public class Animal {
    public String name;
    public Animal(){}
    public Animal(String name){
        this.name=name;
    }
    public void eat(){
        System.out.println(name+": "+"I am eating something.");
    }
}

Cat class:

package org.kuilz.StrategyPattern.original;
public class Cat extends Animal{
    public Cat(){}
    public Cat(String name){
        super(name);
    }
    @Override
    public void eat() {
        System.out.println(name+": "+"I am eating fish.");
    }
}

Dog class:

package org.kuilz.StrategyPattern.original;

public class Dog extends Animal{
    public Dog(){}
    public Dog(String name){
        super(name);
    }
    @Override
    public void eat() {
        System.out.println(name+": "+"I am eating meat.");
    }
}

Main function:

package org.kuilz.StrategyPattern.original;

public class Demo {
    public static void main(String[] args) {
        Animal animal = new Animal("X-animal");
        animal.eat();
        Animal cat = new Cat("Garfield");
        cat.eat();
        Animal dog = new Dog("Snoopy");
        dog.eat();
    }
}

Output:

X-animal: I am eating something.
Garfield: I am eating fish.
Snoopy: I am eating meat.

上面的代码非常简单,Cat和Dog继承了Animal,实现了自己的eat()方法。那么我刚刚说的两个问题体现在哪里呢?

1. 父类有的行为它都会有。
2. 它的行为不能动态改变。

先来看第一条,“父类有的行为它都会有”,继承的特点就是这样,这有什么问题吗?问题出现在我们需要添加新的方法时。 比如老板说,动物怎么可以只会吃东西呢,还要能陪主人玩。然后你就在Animal类中添加一个play()方法,结果发现狗子是挺乐意陪主人玩的,但有些高冷的猫主子并不认为自己需要这个方法,可只要是继承自Animal类的子类都要有该行为,这就出现了问题。

Designer

重点说说第二条,身为大学牲的你天天吃食堂,不出一个月就腻了,狗子也想换换口味,不想天天吃肉,也想啃啃骨头或者常常鱼的味道。很遗憾,刚刚的设计不能实现,也就是说Dog类的eat()方法定义好之后就不能再改变。(你别说把食物作为参数传进去就行了,如果不同动物的eat()方法只是字符串不同的话,那我们连Dog和Cat都不需要定义,这里的eat()其实是很复杂的行为😎)

解决方案是什么呢?我已经忍不住要说了啊啊啊

解决方案就是把行为behavior(即这里的eat和paly)抽取出来,然后定义一组实现该行为的类,Animal拥有这些实现类的对象,并提供对应的setter用于动态修改这些行为。 直接看示例:

Animal class:

package org.kuilz.StrategyPattern.upgrade;

public class Animal {
    public String name;
    private EatBehavior eatBehavior;
    public Animal(){}
    public Animal(String name){
        this.name=name;
    }

    public void setEatBehavior(EatBehavior eatBehavior){
        this.eatBehavior=eatBehavior;
    }
    public void performEat(){
        eatBehavior.eat(this.name);
    }
}

Cat class:

package org.kuilz.StrategyPattern.upgrade;

public class Cat extends Animal {
    public Cat(){}
    public Cat(String name){
        super(name);
    }
}

Dog class:

package org.kuilz.StrategyPattern.upgrade;

public class Dog extends Animal {
    public Dog(){}
    public Dog(String name){
        super(name);
    }
}

EatBehavior interface:

package org.kuilz.StrategyPattern.upgrade;

public interface EatBehavior {
    public abstract void eat(String name);
}

EatFish class:

package org.kuilz.StrategyPattern.upgrade;

public class EatFish implements EatBehavior{
    @Override
    public void eat(String name) {
        System.out.println(name+": "+"I am eating fish.");
    }
}

EatMeat class:

package org.kuilz.StrategyPattern.upgrade;

public class EatMeat implements EatBehavior{
    @Override
    public void eat(String name) {
        System.out.println(name+": "+"I am eating meat.");
    }
}

EatBone class:

package org.kuilz.StrategyPattern.upgrade;

public class EatBone implements EatBehavior{
    @Override
    public void eat(String name) {
        System.out.println(name+": "+"I am eating bones.");
    }
}

Main function:

package org.kuilz.StrategyPattern.upgrade;

import java.util.ArrayList;
import java.util.List;

public class Demo {
    public static void main(String[] args) {
        EatBehavior eatFish = new EatFish();
        EatBehavior eatBone = new EatBone();
        EatBehavior eatMeat = new EatMeat();
        Animal cat = new Cat("Garfield");
        cat.setEatBehavior(eatMeat);
        cat.performEat();
        List<EatBehavior> eatBehaviors=new ArrayList<>();
        eatBehaviors.add(eatFish);
        eatBehaviors.add(eatBone);
        eatBehaviors.add(eatMeat);
        Animal dog = new Dog("Snoopy");
        for(EatBehavior eatBehavior:eatBehaviors){
            dog.setEatBehavior(eatBehavior);
            dog.performEat();
        }
    }
}

Output:

Garfield: I am eating meat.
Snoopy: I am eating fish.
Snoopy: I am eating bones.
Snoopy: I am eating meat.

这样就实现了动态修改,想吃什么吃什么(hhh是不是能看出来我更喜欢狗子。

EatBehavior我已经实现好啦,你可以试着实现PlayBehavior,源码放在github上,可以修改或运行一下加深印象。(如果点个⭐就更好啦😁)

最后说一下策略模式的定义:

The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

策略模式定义了一系列算法,每一个都单独封装起来,并使它们可以相互替换。策略模式使算法能够独立于使用它的客户而变化。

总结来说没什么可总结的,就是字面意思:定义一系列算法或者说一组类,这些类可以相互替换,从而实现了代码复用和动态变化。 书中有一个示例可以加深理解,试一下你能不能秒懂!

Character is the abstract class for all the other characters (King, Queen, Knight, and Troll), while WeaponBehavior is an interface that all weapon behaviors implement. So all actual characters and weapons are concrete classes.

To switch weapons, each character calls the setWeapon() method, which is defined in the Character superclass. During a fight the useWeapon() method is called on the current weapon set for a given character to inflict great bodily damage on another character.

image-20240510184347410

这个示例就是国王和王后等不同角色均继承(IS-A)自Character,Character拥有(HAS-A)WeaponBehavior的实例,以及设置该实例的setter方法。WeaponBehavior是一个接口,有一组实现类。各种角色可以在运行时动态修改使用的武器。

附录:适合中国宝宝体质的笔记

声明:以下内容非原创,定义出自《Head First Design Pattern》类图JDK出自Java 全栈知识体系

定义

The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

策略模式定义了一系列算法,每一个都单独封装起来,并使它们可以相互替换。策略模式使算法能够独立于使用它的客户而变化。

类图

img

JDK

(这些实现我还没看过。。。)

参考

  1. Code Repo
  2. 《Head First Design Pattern》
  3. Java 全栈知识体系
💬评论