自我审视记录本_春天重新审视战略模式

自我审视记录本

这篇博客文章希望展示另一种方法,该方法如何通过依赖注入实现策略模式。 作为DI框架,我选择Spring框架

首先,让我们看一下如何以经典方式实施策略模式。
作为起点,我们有一个HeroController ,应该在HeroRepository添加英雄, HeroRepository取决于用户选择的存储库。

 package com.github.sparsick.springbootexample.hero.universum;  import org.springframework.stereotype.Controller;  import org.springframework.web.bind.annotation.ModelAttribute;  import org.springframework.web.bind.annotation.PostMapping;  @Controller  public class HeroControllerClassicWay { 
     @PostMapping ( "/hero/new" ) 
     public String addNewHero( @ModelAttribute ( "newHero" ) NewHeroModel newHeroModel) { 
         HeroRepository heroRepository = findHeroRepository(newHeroModel.getRepository()); 
         heroRepository.addHero(newHeroModel.getHero()); 
         return "redirect:/hero" ; 
     } 
     private HeroRepository findHeroRepository(String repositoryName) { 
         if (repositoryName.equals( "Unique" )) { 
             return new UniqueHeroRepository(); 
         } 
         if (repositoryName.equals(( "Duplicate" )){ 
             return new DuplicateHeroRepository(); 
         } 
         throw new IllegalArgumentException(String.format( "Find no repository for given repository name [%s]" , repositoryName)); 
     }  } 
 package com.github.sparsick.springbootexample.hero.universum;  import java.util.Collection;  import java.util.HashSet;  import java.util.Set;  import org.springframework.stereotype.Repository;  @Repository  public class UniqueHeroRepository implements HeroRepository { 
     private Set<Hero> heroes = new HashSet<>(); 
     @Override 
     public String getName() { 
         return "Unique" ; 
     } 
     @Override 
     public void addHero(Hero hero) { 
         heroes.add(hero); 
     } 
     @Override 
     public Collection<Hero> allHeros() { 
         return new HashSet<>(heroes); 
     }  } 
 package com.github.sparsick.springbootexample.hero.universum;  import org.springframework.stereotype.Repository;  import java.util.ArrayList;  import java.util.Collection;  import java.util.List;  @Repository  public class DuplicateHeroRepository implements HeroRepository { 
     private List<Hero> heroes = new ArrayList<>(); 
     @Override 
     public void addHero(Hero hero) { 
         heroes.add(hero); 
     } 
     @Override 
     public Collection<Hero> allHeros() { 
         return List.copyOf(heroes); 
     } 
     @Override 
     public String getName() { 
         return "Duplicate" ; 
     }  } 

此实现有一些陷阱。 存储库实现的创建不受Spring Context的管理(它打破了依赖注入/控制逆向)。 一旦您想使用需要注入其他类的其他功能扩展存储库实现,这将很痛苦(例如,使用MeterRegistry计算此类的使用情况)。

 package com.github.sparsick.springbootexample.hero.universum;  import java.util.Collection;  import java.util.HashSet;  import java.util.Set;  import io.micrometer.core.instrument.Counter;  import io.micrometer.core.instrument.MeterRegistry;  import org.springframework.stereotype.Repository;  @Repository  public class UniqueHeroRepository implements HeroRepository { 
     private Set<Hero> heroes = new HashSet<>(); 
     private Counter addCounter; 
     public UniqueHeroRepository(MeterRegistry meterRegistry) { 
         addCounter = meterRegistry.counter( "hero.repository.unique" ); 
     } 
     @Override 
     public String getName() { 
         return "Unique" ; 
     } 
     @Override 
     public void addHero(Hero hero) { 
         addCounter.increment(); 
         heroes.add(hero); 
     } 
     @Override 
     public Collection<Hero> allHeros() { 
         return new HashSet<>(heroes); 
     }  } 

这也打破了关注的分离。 当我想测试控制器类时,我不可能轻松地模拟存储库接口。 因此,第一个想法是将存储库实现的创建置于Spring上下文中。 库实现使用@Repository批注进行批注。 因此,Spring的组件扫描找到了它们。
接下来的问题是如何将它们注入控制器类。 在这里,Spring功能可以提供帮助。 我在控制器中定义了HeroRepository的列表。 在创建控制器实例的过程中必须填写此列表。

 package com.github.sparsick.springbootexample.hero.universum;  import org.springframework.stereotype.Controller;  import org.springframework.web.bind.annotation.ModelAttribute;  import org.springframework.web.bind.annotation.PostMapping;  import java.util.List;  @Controller  public class HeroControllerRefactoringStep1 { 
     private List<HeroRepository> heroRepositories; 
     public HeroControllerRefactoringStep1(List<HeroRepository> heroRepositories) { 
         this .heroRepositories = heroRepositories; 
     } 
     @PostMapping ( "/hero/new" ) 
     public String addNewHero( @ModelAttribute ( "newHero" ) NewHeroModel newHeroModel) { 
         HeroRepository heroRepository = findHeroRepository(newHeroModel.getRepository()); 
         heroRepository.addHero(newHeroModel.getHero()); 
         return "redirect:/hero" ; 
     } 
     private HeroRepository findHeroRepository(String repositoryName) { 
         return heroRepositories.stream() 
                 .filter(heroRepository -> heroRepository.getName().equals(repositoryName)) 
                 .findFirst() 
                 .orElseThrow(()-> new IllegalArgumentException(String.format( "Find no repository for given repository name [%s]" , repositoryName))); "Find no repository for given repository name [%s]" , repositoryName))); 
     }  } 

Spring在其上下文中搜索HeroRepostiory接口的所有实现,并将它们全部放入列表中。 该解决方案的一个缺点是,每个添加了英雄的人都会浏览HeroRepository列表以找到正确的实现。 可以通过在控制器构造函数中创建一个以存储库名称为键,对应的实现为值的映射来优化此映射。

 package com.github.sparsick.springbootexample.hero.universum;  import org.springframework.stereotype.Controller;  import org.springframework.web.bind.annotation.ModelAttribute;  import org.springframework.web.bind.annotation.PostMapping;  import java.util.HashMap;  import java.util.List;  import java.util.Map;  @Controller  public class HeroControllerRefactoringStep2 { 
     private Map<String, HeroRepository> heroRepositories; 
     public HeroControllerRefactoringStep2(List<HeroRepository> heroRepositories) { 
         this .heroRepositories = heroRepositoryStrategies(heroRepositories); 
     } 
     private Map<String, HeroRepository> heroRepositoryStrategies(List<HeroRepository> heroRepositories){ 
         Map<String, HeroRepository> heroRepositoryStrategies = new HashMap<>(); 
         heroRepositories.forEach(heroRepository -> heroRepositoryStrategies.put(heroRepository.getName(), heroRepository)); 
         return heroRepositoryStrategies; 
     } 
     @PostMapping ( "/hero/new" ) 
     public String addNewHero( @ModelAttribute ( "newHero" ) NewHeroModel newHeroModel) { 
         HeroRepository heroRepository = findHeroRepository(newHeroModel.getRepository()); 
         heroRepository.addHero(newHeroModel.getHero()); 
         return "redirect:/hero" ; 
     } 
     private HeroRepository findHeroRepository(String repositoryName) { 
         HeroRepository heroRepository = heroRepositories.get(repositoryName); 
         if (heroRepository != null ) { 
             return heroRepository; 
         } 
         throw new IllegalArgumentException(String.format( "Find no repository for given repository name [%s]" , repositoryName)); 
     }  } 

最后一个问题是应用程序中的其他类是否需要在运行时选择存储库实现的可能性。 我可以将私有方法复制并粘贴到每个有此需求的类中,或者将地图的创建移至Spring Context并将Map注入每个类。

 package com.github.sparsick.springbootexample.hero;  import com.github.sparsick.springbootexample.hero.universum.HeroRepository;  import org.springframework.boot.SpringApplication;  import org.springframework.boot.autoconfigure.SpringBootApplication;  import org.springframework.context.annotation.Bean;  import java.util.HashMap;  import java.util.List;  import java.util.Map;  @SpringBootApplication  public class HeroApplicationRefactoringStep3 { 
     public static void main(String[] args) { 
         SpringApplication.run(HeroApplication. class , args); 
     } 
     @Bean 
     Map<String, HeroRepository> heroRepositoryStrategy(List<HeroRepository> heroRepositories){ 
         Map<String, HeroRepository> heroRepositoryStrategy = new HashMap<>(); 
         heroRepositories.forEach(heroRepository -> heroRepositoryStrategy.put(heroRepository.getName(), heroRepository)); 
         return heroRepositoryStrategy; 
     }  } 
 package com.github.sparsick.springbootexample.hero.universum;  import org.springframework.stereotype.Controller;  import org.springframework.ui.Model;  import org.springframework.web.bind.annotation.ModelAttribute;  import org.springframework.web.bind.annotation.PostMapping;  import java.util.Map;  @Controller  public class HeroControllerRefactoringStep3 { 
     private Map<String, HeroRepository> heroRepositoryStrategy; 
     public HeroControllerRefactoringStep3(Map<String, HeroRepository> heroRepositoryStrategy) { 
         this .heroRepositoryStrategy = heroRepositoryStrategy; 
     } 
     @PostMapping ( "/hero/new" ) 
     public String addNewHero( @ModelAttribute ( "newHero" ) NewHeroModel newHeroModel) { 
         HeroRepository heroRepository = findHeroRepository(newHeroModel.getRepository()); 
         heroRepository.addHero(newHeroModel.getHero()); 
         return "redirect:/hero" ; 
     } 
     private HeroRepository findHeroRepository(String repositoryName) { 
         return heroRepositoryStrategy.get(repositoryName); 
     }  } 

这个解决方案有点丑陋,因为使用策略模式并不明显。 因此,下一个重构步骤是将英雄存储库地图移至自己的组件类。 因此,可以删除应用程序配置中的bean定义heroRepositoryStrategy

 package com.github.sparsick.springbootexample.hero.universum;  import org.springframework.stereotype.Component;  import java.util.Collection;  import java.util.HashMap;  import java.util.Map;  import java.util.Set;  @Component  public class HeroRepositoryStrategy { 
     private Map<String, HeroRepository> heroRepositoryStrategies; 
     public HeroRepositoryStrategy(Set<HeroRepository> heroRepositories) { 
         heroRepositoryStrategies = createStrategies(heroRepositories); 
     } 
     HeroRepository findHeroRepository(String repositoryName) { 
         return heroRepositoryStrategies.get(repositoryName); 
     } 
     Set<String> findAllHeroRepositoryStrategyNames () { 
         return heroRepositoryStrategies.keySet(); 
     } 
     Collection<HeroRepository> findAllHeroRepositories(){ 
         return heroRepositoryStrategies.values(); 
     } 
     private Map<String, HeroRepository> createStrategies(Set<HeroRepository> heroRepositories){ 
         Map<String, HeroRepository> heroRepositoryStrategies = new HashMap<>(); 
         heroRepositories.forEach(heroRepository -> heroRepositoryStrategies.put(heroRepository.getName(), heroRepository)); 
         return heroRepositoryStrategies; 
     }  } 
 package com.github.sparsick.springbootexample.hero.universum;  import org.springframework.stereotype.Controller;  import org.springframework.ui.Model;  import org.springframework.web.bind.annotation.GetMapping;  import org.springframework.web.bind.annotation.ModelAttribute;  import org.springframework.web.bind.annotation.PostMapping;  import java.net.Inet4Address;  import java.net.UnknownHostException;  import java.util.ArrayList;  import java.util.List;  import java.util.Map;  @Controller  public class HeroController { 
     private HeroRepositoryStrategy heroRepositoryStrategy; 
     public HeroController(HeroRepositoryStrategy heroRepositoryStrategy) { 
         this .heroRepositoryStrategy = heroRepositoryStrategy; 
     } 
     @PostMapping ( "/hero/new" ) 
     public String addNewHero( @ModelAttribute ( "newHero" ) NewHeroModel newHeroModel) { 
         HeroRepository heroRepository = heroRepositoryStrategy.findHeroRepository(newHeroModel.getRepository()); 
         heroRepository.addHero(newHeroModel.getHero()); 
         return "redirect:/hero" ; 
     }  } 

整个示例托管在GitHub上

翻译自: https://www.javacodegeeks.com/2019/09/strategy-pattern-revisited-spring.html

自我审视记录本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值