Java设计模式之策略模式

策略模式是一种常用的模式,在日常使用中非常频繁

策略模式是一种行为设计模式,它允许在运行时选择算法的行为。该模式定义了一系列算法,将每个算法封装在单独的类中,并使它们可以互相替换。这使得算法可以独立于其使用者而变化,同时还能保持封装和易于维护的特性。

以下是策略模式的几个主要作用:

1.封装算法:策略模式将不同的算法封装在各自的类中,使得每个算法的实现细节被隐藏在其内部。这样可以避免算法的具体实现对客户端代码的依赖,提高了代码的模块化和可维护性。

2.易于扩展和维护:由于每个算法都是一个独立的类,因此在需要新增、修改或者删除算法时,只需添加、修改或者删除相应的类即可,而不会影响到其他部分的代码。这种低耦合性使得系统更加灵活,易于扩展和维护。

3.多态性:策略模式利用了多态的特性,使得客户端可以统一地对待不同的算法对象。通过定义一个公共的接口或者抽象类,每个具体的算法类都实现了这个接口或者继承了这个抽象类,从而使得它们可以被统一地使用。

4.动态切换算法:由于策略模式允许在运行时动态地选择算法,因此可以根据不同的需求或者条件来灵活地切换算法,而不需要修改客户端的代码。这种动态性使得系统更加灵活和可定制。

5.单一职责原则:策略模式遵循了单一职责原则,每个算法类都专注于实现一个特定的算法,使得每个类的功能单一、清晰,易于理解和维护。

总之,策略模式能够有效地解耦算法的实现和使用,提高代码的灵活性、可维护性和可扩展性,是一种常用的设计模式之一。

而且策略模式的实现方式也有很多种

1.基本策略模式:这是最常见的策略模式,它包括一个策略接口、多个具体策略类和一个上下文类。在上下文类中持有一个策略对象,并将调用委托给策略对象。

2.使用函数式接口:在Java 8及以上版本中,可以使用函数式接口来实现策略模式。函数式接口可以作为参数传递给方法,从而实现策略的灵活切换。

3.枚举策略模式:通过使用枚举来实现策略模式,每个枚举常量代表一个具体的策略。这种方式可以使得策略类的定义更加简洁,并且避免了实现多个策略类的开销。

4.注解策略模式:通过在策略类上使用注解来标识策略,并使用反射来动态加载策略类。这种方式可以使得策略的配置更加灵活,可以在不修改代码的情况下新增或者删除策略。

5.反射策略模式:在上下文类中使用反射来动态加载策略类,这种方式可以在运行时动态地选择和加载策略,而不需要在编译时确定。

6.配置文件策略模式:将策略类的配置信息存储在外部配置文件中,然后在运行时根据配置文件来选择加载策略类。这种方式可以使得策略的配置更加灵活,可以在不修改代码的情况下修改策略。

每种写法都有其优缺点,具体选择取决于应用程序的需求、团队的技术栈和偏好。重要的是要选择最适合你的情况的实现方式,并且保持一致性,以便于代码的维护和理解。

这里我只讲一下两种简单实用的方式

首先是基本策略模式(包括一个策略接口、多个具体策略类和一个上下文类。在上下文类中持有一个策略对象,并将调用委托给策略对象)
比如我们常用的Comparator接口,我们定义了一个比较方法(策略接口)

public interface Comparator<T> {

    int compare(T o1, T o2);
}

然后还需要一个具体的策略类(比如我要比较狗的食量大小)

public class DogComparator implements Comparator<Dog> {
    @Override
    public int compare(Dog o1, Dog o2) {
        if (o1.food < o2.food)
            return -1;
        else if (o1.food > o2.food)
            return 1;
        else
            return 0;
    }
}

这里还需要一个狗对象

public class Dog {

    int food;

    public Dog(int food) {
        this.food = food;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "food=" + food +
                '}';
    }
}

最后还需要一个上下文类

public class Sorter<T> {

    // 从小到大
    public void sort(T[] arr, Comparator<T> comparator) {
        for (int i = 0; i < arr.length; i++) {
            for (int j = i + 1; j < arr.length; j++) {
                if (comparator.compare(arr[i], arr[j]) > 0) {
                    T temp = arr[i];
                    arr[i] = arr[j];
                    arr[j] = temp;
                }
            }
        }
    }

    // 从大到小
    public void sort1(T[] arr, Comparator<T> comparator) {
        for (int i = 0; i < arr.length; i++) {
            for (int j = i + 1; j < arr.length; j++) {
                if (comparator.compare(arr[i], arr[j]) < 0) {
                    T temp = arr[i];
                    arr[i] = arr[j];
                    arr[j] = temp;
                }
            }
        }
    }

}

然后写个demo测试一下

public class Test {

    public static void main(String[] args) {
        Dog[] dogs = new Dog[]{new Dog(3), new Dog(1), new Dog(2)};
        Sorter<Dog> sorter = new Sorter<>();
        sorter.sort(dogs, new DogComparator());
        System.out.println(Arrays.toString(dogs));
    }
}

运行结果

在这里插入图片描述

看到这里很多人肯定是有疑问的,我就比较一个狗的食量大小怎么这么复杂?先不要急,模式之所以称之为模式,那么说明它不是用来处理单一的问题的,而是用来处理某一些相同类型的问题的。

比如接下来我们来了一只猫,它也要比较大小

public class Cat {
    int weight, height;

    public Cat(int weight, int height) {
        this.weight = weight;
        this.height = height;
    }

    @Override
    public String toString() {
        return "Cat{" +
                "weight=" + weight +
                ", height=" + height +
                '}';
    }
}

但是怎么比呢?比如我想根据体重比,这个时候策略模式的优点就能体现出来了
我们添加一个根据体重比较的策略类

public class CatWeightComparator implements Comparator<Cat> {
    @Override
    public int compare(Cat o1, Cat o2) {
        if (o1.weight < o2.weight)
            return -1;
        else if (o1.weight > o2.weight)
            return 1;
        else
            return 0;
    }
}

一样的我们执行一下

public class Test {

    public static void main(String[] args) {
//        Dog[] dogs = new Dog[]{new Dog(3), new Dog(1), new Dog(2)};
//        Sorter<Dog> sorter = new Sorter<>();
//        sorter.sort(dogs, new DogComparator());
//        System.out.println(Arrays.toString(dogs));

        Cat[] cats = new Cat[]{new Cat(3, 3), new Cat(1, 1), new Cat(2, 2)};
        Sorter<Cat> sorter = new Sorter<>();
        sorter.sort(cats, new CatWeightComparator());
        System.out.println(Arrays.toString(cats));
    }
}

结果是正常的

在这里插入图片描述
我还想根据身高比,那么加一个身高的策略类

public class CatHeightComparator implements Comparator<Cat> {
    @Override
    public int compare(Cat o1, Cat o2) {
        if (o1.height < o2.height)
            return -1;
        else if (o1.height > o2.height)
            return 1;
        else
            return 0;
    }
}

这里我们调用sort1方法,让它从大到小排序

public class Test {

    public static void main(String[] args) {
//        Dog[] dogs = new Dog[]{new Dog(3), new Dog(1), new Dog(2)};
//        Sorter<Dog> sorter = new Sorter<>();
//        sorter.sort(dogs, new DogComparator());
//        System.out.println(Arrays.toString(dogs));

//        Cat[] cats = new Cat[]{new Cat(3, 3), new Cat(1, 1), new Cat(2, 2)};
//        Sorter<Cat> sorter = new Sorter<>();
//        sorter.sort(cats, new CatWeightComparator());
//        System.out.println(Arrays.toString(cats));

        Cat[] cats = new Cat[]{new Cat(3, 3), new Cat(1, 1), new Cat(2, 2)};
        Sorter<Cat> sorter = new Sorter<>();
        sorter.sort1(cats, new CatHeightComparator());
        System.out.println(Arrays.toString(cats));
    }
}

看一下结果

在这里插入图片描述
可以发现当我们来了新的对象比如鸡鸭鱼肉的,我们想比较他们的任意属性只需要新写一个鸡鸭鱼肉的属性策略类就可以直接用了,想用其它算法也只需要在上下文类中添加就可以了,它们之间互相不关联不影响,这就是策略模式的作用!

好了下面我们再来看一下使用函数式接口来实现策略模式
这个是我比较喜欢用的一种方式,如果以前你只听说过策略模式可以替代if else但不知道怎么替代,那么下面这个就可以解释你的疑惑
这里我直接上我以前的代码,就不单独写示例了

import lombok.SneakyThrows;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

/**
 * @author Sakura
 * @date 2023/12/19 11:16
 */
@Service
public class CallbackListenerStrategyContext {
    // 策略采用lambda的方法存储
    Map<String, Function<CallbackRoomInfoRequest, CallbackResponse>> strategyContextMap = new HashMap<>();

    @Resource
    CallbackListenerService callbackListenerService;

    @PostConstruct
    @SneakyThrows
    public void setStrategyMap() {
        // 配置回调方法
        strategyContextMap.put("createRoom",(callbackRoomInfoRequest)-> callbackListenerService.createRoom(callbackRoomInfoRequest));
        strategyContextMap.put("destroyRoom",(callbackRoomInfoRequest)-> callbackListenerService.destroyRoom(callbackRoomInfoRequest));
        strategyContextMap.put("updateRoomInfo",(callbackRoomInfoRequest)-> callbackListenerService.updateRoomInfo(callbackRoomInfoRequest));
        strategyContextMap.put("enterRoom",(callbackRoomInfoRequest)-> callbackListenerService.enterRoom(callbackRoomInfoRequest));
        strategyContextMap.put("exitRoom",(callbackRoomInfoRequest)-> callbackListenerService.exitRoom(callbackRoomInfoRequest));
        strategyContextMap.put("takeSeat",(callbackRoomInfoRequest)-> callbackListenerService.takeSeat(callbackRoomInfoRequest));
        strategyContextMap.put("leaveSeat",(callbackRoomInfoRequest)-> callbackListenerService.leaveSeat(callbackRoomInfoRequest));

    }

    public CallbackResponse callbackListener(String command, CallbackRoomInfoRequest callbackRoomInfoRequest){
        // 根据command获取对应的方法返回策略
        Function<CallbackRoomInfoRequest, CallbackResponse> callbackListenerFunc = strategyContextMap.get(command);
        return callbackListenerFunc.apply(callbackRoomInfoRequest);
    }
}

这段代码展示了一个使用 Lambda 表达式实现策略模式的示例。

1.CallbackListenerStrategyContext 类是策略模式中的上下文类。它包含了一个strategyContextMap,用于存储不同策略的 Lambda 表达式。

2.在 setStrategyMap 方法中,通过调用 strategyContextMap 的 put 方法,将命令字符串和相应的Lambda 表达式映射存储到 strategyContextMap 中。

3.在 callbackListener 方法中,根据传入的命令字符串 command,从 strategyContextMap 中获取相应的Lambda 表达式,并执行该 Lambda 表达式,传入 callbackRoomInfoRequest 参数,得到回调响应结果。

这样,通过使用 Lambda表达式,你无需编写具体的策略类,而是直接在上下文类中定义了策略,从而实现了策略模式。这种实现方式简洁而灵活,使得代码更加易于理解和维护。

下面是具体的方法实现

import lombok.extern.java.Log;
import org.springframework.stereotype.Service;

/**
 * @author Sakura
 * @date 2023/12/19 11:42
 */
@Service
@Log
public class CallbackListenerService {

    // 此处用来统一处理多人视频房间回调++++++++++++++++++++++++++++++++++++++++++++

    // 创建房间回调
    public CallbackResponse createRoom(CallbackRoomInfoRequest callbackRoomInfoRequest){
        log.info("创建房间回调" + callbackRoomInfoRequest.toString());
        return CallbackResponse.success();
    }

    // 销毁房间回调
    public CallbackResponse destroyRoom(CallbackRoomInfoRequest callbackRoomInfoRequest){
        log.info("销毁房间回调" + callbackRoomInfoRequest.toString());
        return CallbackResponse.success();
    }

    // 更新房间回调
    public CallbackResponse updateRoomInfo(CallbackRoomInfoRequest callbackRoomInfoRequest){
        log.info("更新房间回调" + callbackRoomInfoRequest.toString());
        return CallbackResponse.success();
    }

    // 用户进房回调
    public CallbackResponse enterRoom(CallbackRoomInfoRequest callbackRoomInfoRequest){
        log.info("用户进房回调" + callbackRoomInfoRequest.toString());
        return CallbackResponse.success();
    }

    // 用户退房回调
    public CallbackResponse exitRoom(CallbackRoomInfoRequest callbackRoomInfoRequest){
        log.info("用户退房回调" + callbackRoomInfoRequest.toString());
        return CallbackResponse.success();
    }

    // 用户上麦回调
    public CallbackResponse takeSeat(CallbackRoomInfoRequest callbackRoomInfoRequest){
        log.info("用户上麦回调" + callbackRoomInfoRequest.toString());
        return CallbackResponse.success();
    }

    // 用户下麦回调
    public CallbackResponse leaveSeat(CallbackRoomInfoRequest callbackRoomInfoRequest){
        log.info("用户下麦回调" + callbackRoomInfoRequest.toString());
        return CallbackResponse.success();
    }
}

这个用起来也很简单

import com.jonsime.jonsimeShop.callback.CallbackListenerStrategyContext;
import com.jonsime.jonsimeShop.callback.CallbackResponse;
import com.jonsime.jonsimeShop.callback.CallbackRoomInfoRequest;
import lombok.extern.java.Log;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@Log
@RestController
@RequestMapping("/tx/callback")
public class TxController {

    @Autowired
    CallbackListenerStrategyContext callbackListenerStrategyContext;

    @PostMapping("/listener")
    public CallbackResponse callbackListener(@RequestParam Long sdkappid, @RequestParam String command,
                                   @RequestParam String contenttype, @RequestParam String clientip,
                                   @RequestParam String optplatform,
                                   @RequestBody CallbackRoomInfoRequest callbackRoomInfoRequest) {
        log.info("回调监听:" + "sdkappid:" + sdkappid + "  command:" + command + "  " + callbackRoomInfoRequest.toString());

        return callbackListenerStrategyContext.callbackListener(command, callbackRoomInfoRequest);
    }
}

上面这个如果用 if 大概是下面这样的

        if ("createRoom".equals(command)) {
            return callbackListenerService.createRoom(callbackRoomInfoRequest);
        } else if ("destroyRoom".equals(command)) {
            return callbackListenerService.destroyRoom(callbackRoomInfoRequest);
        } else if () {
            
        }

这样一对比…好像差不太多!if else似乎看起来更加简单明了!不过这的确就是函数式接口策略模式,大家习惯一下就好
其它实现方式就不讲了,大家

上一篇:初学php反序列化


下一篇:媒体邀约“51媒体网”电视台媒体邀约的优势分析