3

Spring中实现策略模式示例

 6 months ago
source link: https://www.jdon.com/72602.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Spring中实现策略模式示例

在本教程中,将探索 Spring 框架中的各种策略模式实现,例如列表注入、映射注入和方法注入。

什么是策略模式?
策略模式是一种设计原则,允许您在运行时切换不同的算法或行为。它允许您在不改变应用程序核心逻辑的情况下插入不同的策略,从而使您的代码具有灵活性和适应性。

这种方法适用于为特定功能任务提供不同实现方式,并使系统更能适应变化的情况。它通过将算法细节与应用程序的主要逻辑分离,促进了更模块化的代码结构。

步骤 1:实施策略
把自己想象成一个黑暗巫师,努力与春天一起掌握不可饶恕诅咒的力量。我们的任务是实现所有三种诅咒--Avada Kedavra、Crucio 和 Imperio。之后,我们将在运行时切换不同的诅咒(策略)。

让我们从策略接口开始:

public interface CurseStrategy {  

String useCurse();

String curseName();
}

下一步,我们需要执行所有 "不可饶恕的诅咒":

@Component  
public class CruciatusCurseStrategy implements CurseStrategy {  

@Override  
    public String useCurse() {  
        return "Attack with Crucio!";  
    }  

@Override  
    public String curseName() {  
        return "Crucio";  
    }  
}


@Component  
public class ImperiusCurseStrategy implements CurseStrategy {  

@Override  
    public String useCurse() {  
        return "Attack with Imperio!";  
    }  

@Override  
    public String curseName() {  
        return "Imperio";  
    }  
}

@Component  
public class KillingCurseStrategy implements CurseStrategy {  

@Override  
    public String useCurse() {  
        return "Attack with Avada Kedavra!";  
    }  

@Override  
    public String curseName() {  
        return "Avada Kedavra";  
    }  
}

第 2 步:将诅咒注入 List
Spring 提供了一个神奇的功能,允许我们以 List 的形式注入一个接口的多个实现,这样我们就可以用它来注入策略并在它们之间切换。

但让我们先创建基础:Wizard接口。

public interface Wizard {  
    String castCurse(String name); 
}

我们可以在向导中注入我们的诅咒(策略),并筛选出所需的诅咒。

@Service  
public class DarkArtsWizard implements Wizard {  

private final List<CurseStrategy> curses;  

public DarkArtsListWizard(List<CurseStrategy> curses) {  
        this.curses = curses;  
    }  

@Override  
    public String castCurse(String name) {  
        return curses.stream()  
            .filter(s -> name.equals(s.curseName()))  
            .findFirst()  
            .orElseThrow(UnsupportedCurseException::new)  
            .useCurse();  
    }  
}

如果请求的诅咒不存在,也会产生 UnsupportedCurseException。

public class UnsupportedCurseException extends RuntimeException {  
}

测试
我们可以验证诅咒施放是否有效:

@SpringBootTest  
class DarkArtsWizardTest {  

@Autowired  
    private DarkArtsWizard wizard;  

@Test  
    public void castCurseCrucio() {  
        assertEquals("Attack with Crucio!", wizard.castCurse("Crucio"));  
    }  

@Test  
    public void castCurseImperio() {  
        assertEquals("Attack with Imperio!", wizard.castCurse("Imperio"));  
    }  

@Test  
    public void castCurseAvadaKedavra() {  
        assertEquals("Attack with Avada Kedavra!", wizard.castCurse("Avada Kedavra"));  
    }  

@Test  
    public void castCurseExpelliarmus() {  
        assertThrows(UnsupportedCurseException.class, () -> wizard.castCurse("Abrakadabra"));  
    }  
}

另一种流行的方法是定义 canUse 方法,而不是 curseName。这将返回布尔值,并允许我们使用更复杂的过滤功能,例如

public interface CurseStrategy {  

String useCurse();

boolean canUse(String name, String wizardType);
}

@Component  
public class CruciatusCurseStrategy implements CurseStrategy {  

@Override  
    public String useCurse() {  
        return "Attack with Crucio!";  
    }  

@Override  
    public boolean canUse(String name, String wizardType) {  
        return "Crucio".equals(name) && "Dark".equals(wizardType);  
    }  
}

@Service  
public class DarkArtstWizard implements Wizard {  

private final List<CurseStrategy> curses;  

public DarkArtsListWizard(List<CurseStrategy> curses) {  
        this.curses = curses;  
    }  

@Override  
    public String castCurse(String name) {  
        return curses.stream()  
            .filter(s -> s.canUse(name, "Dark")))  
            .findFirst()  
            .orElseThrow(UnsupportedCurseException::new)  
            .useCurse();  
    }  
}

步骤 3:将策略注入Map
我们可以轻松解决上一节中的弊端。Spring 允许我们将 Bean 名称和实例注入 Map。它简化了代码并提高了效率。

@Service  
public class DarkArtsWizard implements Wizard {  

private final Map<String, CurseStrategy> curses;  

public DarkArtsMapWizard(Map<String, CurseStrategy> curses) {  
        this.curses = curses;  
    }  

@Override  
    public String castCurse(String name) {  
        CurseStrategy curse = curses.get(name);  
        if (curse == null) {  
            throw new UnsupportedCurseException();  
        }  
        return curse.useCurse();  
    }  
}

这种方法有一个缺点:Spring 会注入 Bean 名称作为 Map 的键,因此策略名称与 Bean 名称相同,如 cruciatusCurseStrategy。如果 Spring 的代码或我们的类名在未通知的情况下发生变化,这种对 Spring 内部 Bean 名称的依赖可能会导致问题。

让我们检查一下,我们是否仍能施放这些诅咒:

@SpringBootTest  
class DarkArtsWizardTest {  

@Autowired  
    private DarkArtsWizard wizard;  

@Test  
    public void castCurseCrucio() {  
        assertEquals("Attack with Crucio!", wizard.castCurse("cruciatusCurseStrategy"));  
    }  

@Test  
    public void castCurseImperio() {  
        assertEquals("Attack with Imperio!", wizard.castCurse("imperiusCurseStrategy"));  
    }  

@Test  
    public void castCurseAvadaKedavra() {  
        assertEquals("Attack with Avada Kedavra!", wizard.castCurse("killingCurseStrategy"));  
    }  

@Test  
    public void castCurseExpelliarmus() {  
        assertThrows(UnsupportedCurseException.class, () -> wizard.castCurse("Crucio"));  
    }  
}
  • 优点:无循环。
  • 缺点:依赖于 Bean 名称,这使得代码的可维护性较差,并且在名称更改或重构时更容易出错。

步骤 4:注入 List 并将其转换为 Map
如果我们注入 List 并将其转换为 Map,就可以轻松消除 Map 注入的弊端:

@Service  
public class DarkArtsWizard implements Wizard {  

private final Map<String, CurseStrategy> curses;  

public DarkArtsMapWizard(List<CurseStrategy> curses) {  
        this.curses = curses.stream()  
            .collect(Collectors.toMap(CurseStrategy::curseName, Function.identity()));
    }  

@Override  
    public String castCurse(String name) {  
        CurseStrategy curse = curses.get(name);  
        if (curse == null) {  
            throw new UnsupportedCurseException();  
        }  
        return curse.useCurse();  
    }  
}

有了这种方法,我们就可以使用 curseName 代替 Spring 的 Bean 名称作为 Map 键(策略名称)。

步骤 5:接口中的 @Autowire
Spring 支持在方法中自动布线。自动连接到方法的简单示例是通过设置器注入。此功能允许我们在接口的默认方法中使用 @Autowired,这样我们就可以在向导接口中注册每个 CurseStrategy,而无需在每个策略实现中实现注册方法。

让我们通过添加 registerCurse 方法来更新Wizard接口:

public interface Wizard {  

String castCurse(String name);  

void registerCurse(String curseName, CurseStrategy curse)
}
@Service  
public class DarkArtsWizard implements Wizard {  

private final Map<String, CurseStrategy> curses = new HashMap<>();  

@Override  
    public String castCurse(String name) {  
        CurseStrategy curse = curses.get(name);  
        if (curse == null) {  
            throw new UnsupportedCurseException();  
        }  
        return curse.useCurse();  
    }  

@Override  
    public void registerCurse(String curseName, CurseStrategy curse) {  
        curses.put(curseName, curse);  
    }  
}

现在,让我们通过添加带有 @Autowired 注解的方法来更新 CurseStrategy 接口:

public interface CurseStrategy {  

String useCurse();  

String curseName();  

@Autowired  
    default void registerMe(Wizard wizard) {  
        wizard.registerCurse(curseName(), this);  
    }  
}

在注入依赖项的同时,我们将诅咒注册到向导中。

  • 优点:没有循环,也不依赖内部 Spring Bean 名称。
  • 缺点:没有缺点,纯粹的黑魔法。

结论
在本文中,我们探讨了 Spring 环境中的策略模式。我们评估了不同的策略注入方法,并演示了使用 Spring 功能的优化解决方案。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK